WebSocket 连接失败: 在 WebSocket 握手过程中出错: 意外响应代码: 400

我正在尝试将 Socket.io 和 Angular 集成起来,但是我在从客户端到服务器的连接上遇到了困难。我已经看过其他相关的问题,但我的问题是发生在本地,所以没有网络服务器在中间。

我的服务器代码是这样的:

const app = express();
const server = http.createServer(app);
const io = require('socket.io').listen(server);


io.on('connection', function(socket) {
socket.emit('greet', { hello: 'Hey, Mr.Client!' });


socket.on('respond', function(data) {
console.log(data);
});
socket.on('disconnect', function() {
console.log('Socket disconnected');
});
});

我正在使用 Grunt 加载客户端 JavaScript 文件,顺序如下:

dist: {
src: [
public/bower_components/angular/angular.min.js,
...
public/bower_components/socket.io-client/dist/socket.io.min.js,
public/bower_components/angular-socket-io/socket.min.js,
...
]
}

然后在我的控制器里:

function MyController($scope) {


let socket = io.connect(window.location.href);
socket.connect('http://localhost:3000');
socket.on('greet', function(data) {
console.log(data);
socket.emit('respond', { message: 'Hello to you too, Mr.Server!' });
});


...
}

在实际使用 btford/angular-socket-io库之前,我想确保能够正确地获得连接,但是在控制台中会得到以下错误:

socket io connection error message

有趣的是,如果我重新启动 Node.js 服务器进程,它确实能够发送消息,但是使用轮询而不是 websockets。

after restart

polling message

我在 socket.connect 调用中尝试了各种不同的选项,但都没有用。

如果你能帮忙,我将不胜感激。


更新(2016年12月30日) :

我刚意识到 WebSocket 部分正常工作。我在 Chrome 开发者控制台中看到了101个交换协议请求。然而,我看到的唯一帧是 engine. io 协议包(ping,pong)。然而,由于某种原因,我的应用套接字消息仍然回落到轮询..。

engine.io packets

367824 次浏览

In your controller, you are using an http scheme, but I think you should be using a ws scheme, as you are using websockets. Try to use ws://localhost:3000 in your connect function.

I think you should define your origins for client side as bellow:

//server.js
const socket = require('socket.io');
const app = require('express')();
const server = app.listen('port');


const io = socket().attach(server);
io.origins("your_domain:port www.your_domain:port your_IP:port your_domain:*")


io.on('connection', (socket) => {
console.log('connected a new client');
});


//client.js
var socket = io('ws://:port');

Problem solved! I just figured out how to solve the issue, but I would still like to know if this is normal behavior or not.

It seems that even though the Websocket connection establishes correctly (indicated by the 101 Switching Protocols request), it still defaults to long-polling. The fix was as simple as adding this option to the Socket.io connection function:

{transports: ['websocket']}

So the code finally looks like this:

const app = express();
const server = http.createServer(app);
var io = require('socket.io')(server);


io.on('connection', function(socket) {
console.log('connected socket!');


socket.on('greet', function(data) {
console.log(data);
socket.emit('respond', { hello: 'Hey, Mr.Client!' });
});
socket.on('disconnect', function() {
console.log('Socket disconnected');
});
});

and on the client:

var socket = io('ws://localhost:3000', {transports: ['websocket']});
socket.on('connect', function () {
console.log('connected!');
socket.emit('greet', { message: 'Hello Mr.Server!' });
});


socket.on('respond', function (data) {
console.log(data);
});

And the messages now appear as frames:

working websockets

This Github issue pointed me in the right direction. Thanks to everyone who helped out!

Judging from the messages you send via Socket.IO socket.emit('greet', { hello: 'Hey, Mr.Client!' });, it seems that you are using the hackathon-starter boilerplate. If so, the issue might be that express-status-monitor module is creating its own socket.io instance, as per: https://github.com/RafalWilinski/express-status-monitor#using-module-with-socketio-in-project

You can either:

  1. Remove that module
  2. Pass in your socket.io instance and port as websocket when you create the expressStatusMonitor instance like below:

    const server = require('http').Server(app);
    const io = require('socket.io')(server);
    ...
    app.use(expressStatusMonitor({ websocket: io, port: app.get('port') }));
    

This worked for me with Nginx, Node server and Angular 4

Edit your nginx web server config file as:

server {
listen 80;
server_name 52.xx.xxx.xx;


location / {
proxy_set_header   X-Forwarded-For $remote_addr;
proxy_set_header   Host $http_host;
proxy_pass         "http://127.0.0.1:4200";
proxy_http_version 1.1;
proxy_set_header   Upgrade $http_upgrade;
proxy_set_header   Connection "upgrade";
}

I had faced same issues, I refined apache2 virtual host entery and got success.

Note: on server I had succesful installed and working on 9001 port without any issue. This guide line for apache2 only no relavence with nginx, this answer for apache2+etherpad lovers.

<VirtualHost *:80>
ServerName pad.tejastank.com
ServerAlias pad.tejastank.com
ServerAdmin snippetbucket@gmail.com


LoadModule  proxy_module         /usr/lib/apache2/modules/mod_proxy.so
LoadModule  proxy_http_module    /usr/lib/apache2/modules/mod_proxy_http.so
LoadModule  headers_module       /usr/lib/apache2/modules/mod_headers.so
LoadModule  deflate_module       /usr/lib/apache2/modules/mod_deflate.so


ProxyVia On
ProxyRequests Off
ProxyPreserveHost on


<Location />
ProxyPass http://localhost:9001/ retry=0 timeout=30
ProxyPassReverse http://localhost:9001/
</Location>
<Location /socket.io>
# This is needed to handle the websocket transport through the proxy, since
# etherpad does not use a specific sub-folder, such as /ws/ to handle this kind of traffic.
# Taken from https://github.com/ether/etherpad-lite/issues/2318#issuecomment-63548542
# Thanks to beaugunderson for the semantics
RewriteEngine On
RewriteCond %{QUERY_STRING} transport=websocket    [NC]
RewriteRule /(.*) ws://localhost:9001/socket.io/$1 [P,L]
ProxyPass http://localhost:9001/socket.io retry=0 timeout=30
ProxyPassReverse http://localhost:9001/socket.io
</Location>




<Proxy *>
Options FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
allow from all
</Proxy>
</VirtualHost>

Advance tips: Please with help of a2enmod enable all mod of apache2

Restart apache2 than will get effect. But obvious a2ensite to enable site required.

You're using port 3000 on the client-side. I'd hazard a guess that's the Angular port and not the server port? It should be connecting to the server port.

After using following load balancer setting my problem solved for wss but for ws problem still exists for specific one ISP.

calssic-load-balancer

I solved this by changing transports from 'websocket' to 'polling'

   var socket = io.connect('xxx.xxx.xxx.xxx:8000', {
transports: ['polling']
});

I solved this by removing io.listen(server);. I started running into this error when I started integrating passport.socketio and using passport middleware.

The currently accepted solution is misleading.

According to the official documentation, adding the transports: [ 'websocket' ] option effectively removes the ability to fallback to long-polling when the websocket connection cannot be established. This option is what makes socket.io so robust in the first place because it can adapt to many scenarios.

In that particular case where one wishes to solely rely on websockets, directly using the WebSocket API is recommended.

For other cases (supposedly most users), this is most likely a reverse proxy/server configuration problem.

The official documentation suggests the following depending on your environment:

NginX configuration

http {
server {
listen 3000;
server_name io.yourhost.com;


location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;


proxy_pass http://nodes;


# enable WebSockets
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}


upstream nodes {
# enable sticky session based on IP
ip_hash;


server app01:3000;
server app02:3000;
server app03:3000;
}
}

Apache HTTPD configuration

Header add Set-Cookie "SERVERID=sticky.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED


<Proxy "balancer://nodes_polling">
BalancerMember "http://app01:3000" route=app01
BalancerMember "http://app02:3000" route=app02
BalancerMember "http://app03:3000" route=app03
ProxySet stickysession=SERVERID
</Proxy>


<Proxy "balancer://nodes_ws">
BalancerMember "ws://app01:3000" route=app01
BalancerMember "ws://app02:3000" route=app02
BalancerMember "ws://app03:3000" route=app03
ProxySet stickysession=SERVERID
</Proxy>


RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) balancer://nodes_ws/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /(.*) balancer://nodes_polling/$1 [P,L]


ProxyTimeout 3

HAProxy configuration

listen chat
bind *:80
default_backend nodes


backend nodes
option httpchk HEAD /health
http-check expect status 200
cookie io prefix indirect nocache # using the `io` cookie set upon handshake
server app01 app01:3000 check cookie app01
server app02 app02:3000 check cookie app02
server app03 app03:3000 check cookie app03

Also worth reading this on upgrading connections in HAProxy.

For more details please refer to the official documentation link above.

EDIT:

Varnish (source here)

sub vcl_recv {
if (req.http.upgrade ~ "(?i)websocket") {
return (pipe);
}
}


sub vcl_pipe {
if (req.http.upgrade) {
set bereq.http.upgrade = req.http.upgrade;
set bereq.http.connection = req.http.connection;
}
}

if you are using httpd/apache, you can add a file something like ws.conf and add this code to it. Also, this solution can proxy something like this "http://localhost:6001/socket.io" to just this "http://localhost/socket.io"

<VirtualHost *:80>
RewriteEngine on


#redirect WebSocket
RewriteCond %{REQUEST_URI}  ^/socket.io            [NC]
RewriteCond %{QUERY_STRING} transport=websocket    [NC]
RewriteRule /(.*)           ws://localhost:6001/$1 [P,L]


ProxyPass        /socket.io http://localhost:6001/socket.io
ProxyPassReverse /socket.io http://localhost:6001/socket.io
</VirtualHost>

Had the same issue, my app is behind nginx. Making these changes to my Nginx config removed the error.

location / {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}

In my case, I have just install express-status-monitor to get rid of this error

here are the settings

install express-status-monitor
npm i express-status-monitor --save
const expressStatusMonitor = require('express-status-monitor');
app.use(expressStatusMonitor({
websocket: io,
port: app.get('port')
}));

Using Apollo Server 2.

Per https://github.com/apollographql/apollo-client/issues/4778#issuecomment-509638071, this solved my problem:

try 'ws://localhost:4000/graphql'

...since incoming and outgoing requests now use the same address.

The problem for me was not got the port from process.env.PORT it is very important because Heroku and other services properly do a random port numbers to use.

So that is the code that work for me eventuly :

var app = require('express')();
var http = require('http').createServer(app);
const serverPort = process.env.PORT ; //<----- important


const io = require('socket.io')(http,{
cors: {
origin: '*',
methods: 'GET,PUT,POST,DELETE,OPTIONS'.split(','),
credentials: true
}
});


http.listen(serverPort,()=>{
console.log(`server listening on port ${serverPort}`)
})

my problem was with server side

const app = require("express")();
const http = require("http").Server(app);
const io = require("socket.io")(http);

listen with

http.listen(PORT,()=> console.log('listening'))

it was giving me error when i did

app.listen(......)

I had the same error witk socket.io on node.js but the reason was quite silly. There wasn't all socket.io's dependencies installed correctly, namely package base64id was missed

Including transports: ['websocket'] is not the best approach as it removes Sockt.io functionality from suiting any scenario.

In my case using Nodejs + Nginx + Vuejs/Vite(Front) I managed to solve it by configuring the reverse proxy in the site configuration in Nginx.

http {
server {
listen 80;
root /var/www/html;


// <---- start ---->
location /socket.io/ { // <--- Set your custom path
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;


proxy_pass http://localhost:3000; // <--- Set your Port


proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
// <---- End ---->


}
}

You can find references on this issue on the Socket.io website or on Nginx

If you tried every option given above then there is no problem with your code, try to stop the ad blocker of your browser. It worked for me

My Output after disabling blocker Output

My server Server