无法创建到 Node.js socket.io 服务器的跨域 websocket 连接

Posted

技术标签:

【中文标题】无法创建到 Node.js socket.io 服务器的跨域 websocket 连接【英文标题】:Unable to create cross-domain websocket connection to Node.js socket.io server 【发布时间】:2013-12-04 06:37:45 【问题描述】:

根据我的搜索,这应该不难,到目前为止我已经尝试了很多没有运气的事情。我有一个在 Heroku 上运行的 Node.js 服务器,它创建了一个 socket.io 服务器(我也尝试在 Nodejitsu 上托管它,但没有帮助)。如果我从要创建 websocket 连接的实际 Node.js 服务器提供页面,则 socket.io 套接字连接可以正常工作,但我无法使任何跨域套接字连接尝试工作。

我尝试了几种方法跨域连接到这个 socket.io 服务器:

(1) 一个简单的本地 html 文件,其中包含一些脚本,该脚本成功地从节点服务器获取客户端 socket.io 库(使用 $.getScript),然后尝试创建套接字连接(var socket = io.connect(' nodejs_server_hostname:port_num');),失败了。

(2) 在本地运行 rails 应用服务器(开发模式),向包含 javascript 的客户端提供响应,该客户端尝试以与方法 (1) 相同的方式形成 websocket 连接,但失败

(3) 在 Heroku 中托管 rails 应用,和方法 (2) 做同样的事情。这也失败了。我确保启用 Heroku 文档 (https://devcenter.heroku.com/articles/heroku-labs-websockets) 中指定的 websockets。再一次,如果请求是从客户端尝试与之建立 socket.io 连接的实际 Node.js 服务器提供的,那么一切正常。

socket.socket.on('error') 事件在所有三个尝试中都被触发。

我尝试使用以下代码修改 rails 应用程序中的 http 响应标头:

response.headers['Access-Control-Allow-Origin'] = '*'

这会正确设置标题(我在从 rails 服务器请求页面后检查过),但它不能解决套接字连接问题。

我在执行日志记录的 Node.js 服务器上创建了一个 socket-io 授权回调,对于跨域套接字连接尝试来说,这个回调似乎根本没有被命中。所以看起来 Node.js 服务器甚至从来没有看到过 websocket 连接尝试,这让我很困惑。

下面我将粘贴所有相关的代码文件。

Node.js 服务器文件:

var http = require('http'),
    express = require('express'),
    whiskers = require('whiskers'),
    redis = require('redis'),
    port = process.env.PORT || 5000,
    app = express(),
    server = http.createServer(app).listen(port),
    io = require('socket.io').listen(server);

console.log('---- ' + port);
console.log(port);
console.log('---- ' + port);

io.configure(function ()     
    io.set('authorization', function (handshakeData, callback) 
        console.log("-- inside io.set('authorization, cb)")
        console.log('-- handshakeData:');
        console.log(handshakeData);
        console.log('-- end of handshakeData logging --');
        callback(null, true); // error first callback style 
    );
);

app.configure(function() 
    app.use(express.static(__dirname + '/public'));

    app.set('views', __dirname + '/views');
    app.set('view options',  layout: false );

    app.engine('.html', whiskers.__express);
);


app.get('/', function(req, res) 
    res.render('index.html');
);

io.sockets.on('connection', function(socket) 
    console.log('---- new client connected ----');
    console.log(socket);
    console.log('---- end of socket details logging for new client connection ----');

    socket.emit('news',  hello: 'world' );

    socket.on('my other event', function(data) 
        console.log("-- server -- inside socket.on 'my other event' callback");
        console.log(data);
    );

    socket.on('button clicked', function(data) 
        console.log("-- server -- inside socket.on 'button clicked' callback");
        console.log(data);

        io.sockets.emit('news',  info: 'the button was clicked' );
    );
);

这是来自 Rails 应用程序的客户端 javascript 文件:

$(document).ready(function() 
    console.log('-- inside chat.js -- document is ready');

    $('#join-chat-container').hide();
    $('#new-message-container').hide();

    //$.getScript('http://node-server-for-rails-chat.herokuapp.com/socket.io/socket.io.js')
    $.getScript('http://node-server-for-rails-chat.jit.su/socket.io/socket.io.js')
        .done(function( script, textStatus ) 
            console.log("-- socket.io.js loaded and executed -- textStatus: " + textStatus);
            setup_socket();
        )
        .fail(function( jqxhr, settings, exception ) 
            console.log("-- socket.io.js load error -- exception:");
            console.log(exception);
            console.log("-- jqxhr: " + jqxhr);
        );
);

function setup_socket() 
    //var socket = io.connect('node-server-for-rails-chat.herokuapp.com',  port: 19364 );
    //var socket = io.connect('node-server-for-rails-chat.jit.su',  port: 5000 );
    var socket = io.connect('node-server-for-rails-chat.herokuapp.com:27305');

    socket.socket.on('error', function (reason) 
        console.log('--- cant connect');
        console.log(reason);
        console.error('Unable to connect Socket.IO', reason);
    );

    socket.on('connect_failed', function() 
        console.log('connect failed');
    );

    socket.on('connect', function() 
        console.log('successfully connected!');
    );


    socket.on('message', function (message_html) 
        console.log("-- client -- inside socket.on('message', cb) callback");
        console.log("-- client -- message_html: " + message_html);

        add_message(message_html);
    );

    if (!socket.socket.connected) 
        console.log(socket);
        console.log("-- Error connecting with socket");
    
    socket_loaded(socket);
;

function socket_loaded(socket) 
    // this part contains irrelevant (not socket related) code


//more irrelevant code excluded for brevity (add_message function, plus other stuff)

这是我在浏览器本地打开的独立HTML文件:

<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript" src="http://code.jquery.com/jquery-git.js"></script>
    <script>
        $(document).ready(function() 
            if (typeof jQuery !==  'undefined') 
                console.log('-- client -- jQuery library loaded successfully');

                $('#myButton').on('click', function() 
                    console.log("-- client -- inside jquery '#myButton' click callback");
                    socket.emit('button clicked',  button: '#myButton' );
                );
            

            $.getScript('http://node-server-for-rails-chat.herokuapp.com/socket.io/socket.io.js')
                .done(function( script, textStatus ) 
                    console.log("-- socket.io.js loaded and executed -- textStatus: " + textStatus);
                    socket_is_loaded();
                )
                .fail(function( jqxhr, settings, exception ) 
                    console.log("-- socket.io.js load error -- exception:");
                    console.log(exception);
                    console.log("-- jqxhr: " + jqxhr);
                );
        );

        function socket_is_loaded() 
            //var socket = io.connect('node-server-for-rails-chat.jit.su:5000');
            var socket = io.connect('node-server-for-rails-chat.herokuapp.com:27305');

            socket.socket.on('error', function (reason) 
                console.log('--- cant connect');
                console.log(reason);
                console.error('Unable to connect Socket.IO', reason);
            );

            socket.on('connect_failed', function() 
                console.log('connect failed');
            );

            socket.on('connect', function() 
                console.log('successfully connected!');
            );

            socket.on('news', function(data) 
                console.log("-- client -- inside socket.on 'news' callback");
                console.log(data);
                //socket.emit('my other event',  my: 'data' );
            );
        

    </script>
</head>

<body>
    <h1>Socket.io How-to-use Example Script</h1>
    <input type='button' id='myButton' value='click me!'/>
</body>
</html>

有人对我如何解决这个问题有想法吗?非常感谢。

【问题讨论】:

【参考方案1】:

Socket.IO 版本 --> 1.3.7 Express 版本 --> 4.13.3

选项 1:仅强制使用 Websockets

默认情况下,websockets 是跨域的。如果您强制 Socket.io 仅将其用作连接客户端和服务器的方法,那么您就可以开始了。

服务器端

//HTTP Server 
var server = require('http').createServer(app).listen(8888);
var io = require('socket.io').listen(server);

//Allow Cross Domain Requests
io.set('transports', [ 'websocket' ]);

客户端

var connectionOptions =  
            "force new connection" : true,
            "reconnectionAttempts": "Infinity", //avoid having user reconnect manually in order to prevent dead clients after a server restart
            "timeout" : 10000, //before connect_error and connect_timeout are emitted.
            "transports" : ["websocket"]
        ;

 var socket = io("ur-node-server-domain", connectionOptions);

就是这样。问题?不适用于不支持 websockets 的浏览器(对于客户端)。有了这个,你几乎可以扼杀 Socket.io 的魔力,因为它逐渐从长轮询开始,然后升级到 websockets(如果客户端支持的话)。

如果您 100% 确定您的所有客户端都将使用兼容 HTML5 的浏览器进行访问,那么您就可以开始了。

选项 2:在服务器端允许 CORS,让 Socket.io 处理是使用 websockets 还是长轮询。

对于这种情况,您只需要调整服务器端设置。客户端连接一如既往。

服务器端

//HTTP Server 
var express=require('express');
//Express instance
var app = express();

//ENABLE CORS
app.all('/', function(req, res, next) 
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "X-Requested-With");
  next();
 );

就是这样。希望它可以帮助其他人。请记住,对于第二个选项, //ENABLE CORS 部分是您应用到您的 express 实例( express() )的第一件事,这一点非常重要;

【讨论】:

【参考方案2】:

我正在运行类似的设置,并且必须在 node express web 服务器上配置以下标头。

app = require('express')();
...
app.use(function(req, res, next) 
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  return next();
);

【讨论】:

这会带来任何安全风险吗? @pmilla1606,这意味着浏览器可以向您的服务器发出 ajax 请求,即使它们的页面是从不同的域加载的。因此,您将希望您的服务器得到适当的保护。以同样的方式,它将为常规网页加载提供保护。所以,这不是一个巨大的安全问题。

以上是关于无法创建到 Node.js socket.io 服务器的跨域 websocket 连接的主要内容,如果未能解决你的问题,请参考以下文章

无法从Win Forms C#app连接到远程Node.js Socket.IO服务器

Node.js socket.io.js 未找到或 io 未定义

无法获取 socket.io.js

使用Node.js+Socket.IO搭建WebSocket实时应用

从 Nginx 到 express.js 上的 socket.io 的反向代理上的“无法获取”

socket.io 手动将用户添加到房间(node.js)