iT邦幫忙

1

[已解決]Flask Websocket無法從其他伺服器送過來

flask新人問題, 想從不同的伺服器用Websocket收發資料,
主要的Flask Server程式部分:

from flask_socketio import SocketIO
app = Flask(__name__)
app.config.from_object(Config)

@socketio.on('for_test')
def for_test(test):
    print("test")
    
if __name__ == '__main__':
    socketio.run(app, host='0.0.0.0', port=5002, use_reloader=True, debug=True)

test.html:

<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script>
<script type="text/javascript" charset="utf-8">
  $(document).ready(function() {
    var socket = io.connect('http://' + '192.168.1.183' + ':' + '5002');
    $('#aa').click(function(event) {
      socket.emit('for_test');
    });       

<!-- Socket on -->
    
    socket.on('state_message', function(data) {
      console.log(data);
      var $textarea = $('#state_message');
      $textarea.val(data);
    })    
  });    

<button id="aa" class="btn btn-primary btn-lg col-xs-3 col-xs-offset-3">aa</button>

按下'aa按鈕'後會'socket.emit'到'for_test', 然後Server會print出"test".
在這邊將var socket中的IP設為0.0.0.0或192.168.1.183皆可正常執行.

但想從其他伺服器送過來的時候卻都沒反應, 例如把上面這些整個複製、準備另外一份如下:

from flask_socketio import SocketIO
app = Flask(__name__)
app.config.from_object(Config)

@socketio.on('for_test')
def for_test(test):
    print("test")
    
if __name__ == '__main__':
    socketio.run(app, host='0.0.0.0', port=5003, use_reloader=True, debug=True)

在這邊只改了Port到5003, 然後網頁端完全一樣, 理想中的結果是在Port 5003網頁中的'aa按鈕'後, 5002會收到.
但測試結果卻沒有反應, 不清楚是哪裡寫錯, 還是對Websocket的用法理解錯誤了?

(這些都在架在同一台ubuntu伺服器上, 應該沒有被防火牆擋住之類的問題?)


https://ithelp.ithome.com.tw/upload/images/20191209/20123386EYpKoiHe2I.jpg
現在狀況像這張圖, 全部都在同一台server進行. 在5002和5003各建一個flask server, 各自的server和web的websocket功能都正常, 但要是把位置改成另外一台, 例如5002送到5003(這樣在頁面需要同時建立5002:5002和5002:5003嗎?我的理解是只需要5002:5003), websocket會沒反應


後來找到問題了

Cross-Origin Controls
For security reasons, this server enforces a same-origin policy by default. In practical terms, this means the following:

If an incoming HTTP or WebSocket request includes the Origin header, this header must match the scheme and host of the connection URL. In case of a mismatch, a 400 status code response is returned and the connection is rejected.
No restrictions are imposed on incoming requests that do not include the Origin header.
If necessary, the cors_allowed_origins option can be used to allow other origins. This argument can be set to a string to set a single allowed origin, or to a list to allow multiple origins. A special value of '*' can be used to instruct the server to allow all origins, but this should be done with care, as this could make the server vulnerable to Cross-Site Request Forgery (CSRF) attacks.

flask預設不能跨域

設為cors_allowed_origins='*'

就可以了

2 個回答

0
浩瀚星空
iT邦大師 1 級 ‧ 2019-12-09 11:39:08

(這些都在架在同一台ubuntu伺服器上, 應該沒有被防火牆擋住之類的問題?)

還好你後面還是 「?」

其實就算是同一台機器呼叫。還是會有防火的問題存在。
再加上你用了其它port。大多數的port是鎖住的。

基本來說,會先試著將防火關掉再看看是不是防火的問題。
是的話,再打開一個一個調整試試。

看更多先前的回應...收起先前的回應...
picross iT邦新手 5 級 ‧ 2019-12-09 11:46:21 檢舉

sudo iptables -I INPUT -p tcp --dport 5002 -j ACCEPT
我用了這個指令開了5002, 5003, 80結果都一樣

基本我會先試著將iptables 暫時關掉試試。先確定是否是防火的問題。

picross iT邦新手 5 級 ‧ 2019-12-09 11:50:07 檢舉

service iptables stop整個關掉後仍然沒反應, 我自己是比較傾向我有寫錯/images/emoticon/emoticon02.gif

fillano iT邦超人 1 級 ‧ 2019-12-09 11:55:37 檢舉

websocket是瀏覽器跟伺服器之間的連線,如果同時要接5002跟5003,那需要做兩個連線。另外,如果要5002收到5003的東西,那需要透過瀏覽器轉送。我不知道你是否是這樣做?

picross iT邦新手 5 級 ‧ 2019-12-09 12:01:26 檢舉

我現在想要讓5003的網頁送到5002的伺服器, 用前面貼的寫法可以5002網頁送到5002伺服器, 但不知道為什麼不能5003網頁送到5002伺服器.
建立連線我的理解是網頁端的這段和伺服器做連接
var socket = io.connect('http://' + '192.168.1.183' + ':' + '5002');

沒去注意到你說websocket
正常不同PORT的是無法直接互傳的。大多數都是由SERVER來做中間交接處理才對。
這部份我比較不熟門熟路,就請專業的回答你正確的答案了。

2
fillano
iT邦超人 1 級 ‧ 2019-12-09 16:15:42

簡單說,websocket建立的是「瀏覽器」與「伺服器」之間的連線。如果你要兩個伺服器之間傳資料,那得要:

  1. 在頁面中建立與5002的連線
  2. 在頁面中建立與5003的連線
  3. 5002與5003之間傳資料,要用頁面來中轉

所以頁面可能會像這樣:

var socket1 = io.connect('url to 5002');
var socket2 = io.connect('url to 5003');
socket1.on('from_5002', function(data) {
    socket2.emit('to_5003', data);//收到5002的資料,轉送到5003
}

我沒用過flask,所以也不知道他的能力。不過如果他有websocket client的功能,也是有可能用他直接送資料,不透過頁面來中轉就是了。

看更多先前的回應...收起先前的回應...
picross iT邦新手 5 級 ‧ 2019-12-09 17:06:35 檢舉

https://ithelp.ithome.com.tw/upload/images/20191209/20123386EYpKoiHe2I.jpg
現在狀況像這張圖, 全部都在同一台server進行. 在5002和5003各建一個flask server, 各自的server和web的websocket功能都正常, 但要是把位置改成另外一台, 例如5002送到5003(這樣在頁面需要同時建立5002:5002和5002:5003嗎?我的理解是只需要5002:5003), websocket會沒反應

fillano iT邦超人 1 級 ‧ 2019-12-09 18:16:27 檢舉

我用node.js寫:
corssws/index.js:

const args = require('minimist')(process.argv.slice(2));
const app = require('express')();
const http = require('http').createServer(app);
const io = require('socket.io')(http);

if(args._.length > 0) {
    var port = parseInt(args._[0], 10);
    app.get('/', function(req, res){
        res.sendFile(__dirname + '/index.html');
    });
      
    http.listen(port, function(){
        console.log(`listening on *:${port}`);
    });

    io.on('connection', socket => {
        socket.emit('from-5002', 'message from 5002');
        socket.on('to-5003', data => {
            socket.emit('from-5003', `[5003 received] ${data}`);
        });
    });

} else {
    help();
}

function help() {
    console.log('[usage] node crossws [port]');
}

然後html:

<!doctype html>
<html>
    <body>
    <div id="panel"></div>
    <script src="/socket.io/socket.io.js"></script>
    <script>
        var socket1 = io('http://localhost:5002');
        var socket2 = io('http://localhost:5003');
        var hello = false;
        socket1.on('from-5002', data => {
            if(!hello)
                socket2.emit('to-5003', data);
                message(`from 5002: ${data}`);
        });
        socket2.on('from-5003', data => {
            message(`from 5003: ${data}`);
        });
        function message(msg) {
            var t = document.getElementById('panel');
            t.innerHTML += `<br>${msg}`;
        }
    </script>
    </body>
</html>

然後啟動兩個伺服器

> node crossws 5002
> node crossws 5003

開啟網頁( http://localhost:5002 ),會看到

from 5002: message from 5002
from 5003: [5003 received] message from 5002
fillano iT邦超人 1 級 ‧ 2019-12-09 18:20:11 檢舉

(對了,這個例子要裝express, minimist 跟 socket.io)

fillano iT邦超人 1 級 ‧ 2019-12-09 18:24:36 檢舉

html改了一下,有地方沒寫好:

<!doctype html>
<html>
    <body>
    <div id="panel"></div>
    <script src="/socket.io/socket.io.js"></script>
    <script>
        var socket1 = io('http://localhost:5002');
        var socket2 = io('http://localhost:5003');
        var hello = false;
        socket1.on('from-5002', data => {
            if(!hello) {
                socket2.emit('to-5003', data);
                message(`from 5002: ${data}`);
                hello = true;
            }
        });
        socket2.on('from-5003', data => {
            message(`from 5003: ${data}`);
        });
        function message(msg) {
            var t = document.getElementById('panel');
            t.innerHTML += `<br>${msg}`;
        }
    </script>
    </body>
</html>

不影響結果就是了。

froce iT邦大師 2 級 ‧ 2019-12-09 18:47:10 檢舉

python有 socketIO-client 套件可以轉發。

不過我搞不太懂這個應用場景是要幹麻,簡單的chat room一個server就夠了。

fillano iT邦超人 1 級 ‧ 2019-12-10 09:43:01 檢舉

我猜只是為了測試而已,只是想法不太對,所以測不出東西。

fillano iT邦超人 1 級 ‧ 2019-12-10 09:51:25 檢舉

改了一個交互送資料的版本:

index.js

const args = require('minimist')(process.argv.slice(2));
const app = require('express')();
const http = require('http').createServer(app);
const io = require('socket.io')(http);

if(args._.length > 0) {
    var port = parseInt(args._[0], 10);
    app.get('/', function(req, res){
        res.sendFile(__dirname + '/index.html');
    });
      
    http.listen(port, function(){
        console.log(`listening on *:${port}`);
    });

    io.on('connection', socket => {
        socket.emit(`from-${port}`, `message from ${port}`);
        console.log('client connected...');
        socket.on(`to-${port}`, data => {
            console.log(`server ${port} received: ${data}`);
            socket.emit(`from-${port}`, `[${port} received] ${data}`);
        });
    });

} else {
    help();
}

function help() {
    console.log('[usage] node crossws [port]');
}

index.html

<!doctype html>
<head>
    <style>
        #panel {
            border: solid 1px black;
            height: 480px;
            overflow-x: hidden;
            overflow-y: scroll;
            padding: 5px 5px 5px 5px;
        }
    </style>
</head>
<html>
    <body>
    <div id="panel"></div>
    <script src="/socket.io/socket.io.js"></script>
    <script>
        var ports = ['5002', '5003'];
        var count = 0;
        var sockets = ports.reduce((prev, curr) => {
            prev.push(io(`http://localhost:${curr}`));
            return prev;
        }, []);
        sockets.forEach((socket, idx) => {
            socket.on(`from-${ports[idx]}`, data => {
                if(count < 30) {
                    var t = (idx+1)%ports.length;
                    sockets[t].emit(`to-${ports[t]}`, data);
                    message(`from ${ports[idx]}: ${data}, ${count}`);
                    count++;
                }
            });
        });
        function message(msg) {
            var t = document.getElementById('panel');
            t.innerHTML += `${msg}<br><hr size="1" width="100%"/>`;
        }
    </script>
    </body>
</html>

頁面跑出來的結果:
https://ithelp.ithome.com.tw/upload/images/20191210/20000108T4QO5efG5V.png

picross iT邦新手 5 級 ‧ 2019-12-10 14:01:48 檢舉

雖然是js版的還是謝謝, 最後問題是flask預設不能跨域, 設定

cors_allowed_origins='*'

就可以了
socket收發部分是沒有寫錯的

fillano iT邦超人 1 級 ‧ 2019-12-10 15:31:21 檢舉

那就好XD

我要發表回答

立即登入回答