WebSocket 连接错误 Error during WebSocket handshake Unexpected response code 404
一、问题描述
后台 SpringBoot 使用 @ServerEndpoint 创建了一个 websocket 服务端,本地测试的时候一切正常,部署到线上的时候链接报错
复制WebSocket connection to 'ws://xxxx' failed: Error during WebSocket handshake: Unexpected response code: 404
复制代码
当项目使用域名+端口号的方式访问的时候 ws 连接正常,而通过 nginx 反向代理后 ws 连接就不正常了。
错误的 nginx 配置:
复制server{
listen 80;
charset utf-8;
server_name ws.xxx.cn;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 100m;
location / {
proxy_pass http://127.0.0.1:8087;
}
}
复制代码
二、原因分析
Websocket 握手格式包:
复制GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
复制代码
这请求类似 http 协议,里面多了陌生的内容是:
复制Upgrade: websocket
Connection: Upgrade
复制代码
这个就是 Websocket 的相关的,他会告诉 Apache、Nginx 等服务器我发起的是 websocket 请求,不是 http!下面的三个参数 Sec-WebSocket-Key、Sec-WebSocket-Protocol、Sec-WebSocket-Version 作用大概就是验证请求确实是 websocket,同时指定协议版本。
三、解决方法
官方地址:http://nginx.org/en/docs/http/websocket.html
nginx 配置 WebSocket 代理
要将客户端和服务器之间的连接从 HTTP / 1.1 转换为 WebSocket,将使用 HTTP / 1.1 中可用的协议切换机制。
但是,有一个微妙之处:由于“升级”是 逐跳的 标头,因此它不会从客户端传递到代理服务器。使用正向代理,客户端可以使用该 CONNECT 方法来规避此问题。但是,这不适用于反向代理,因为客户端不知道任何代理服务器,并且需要对代理服务器进行特殊处理。
从版本 1.3.13 开始,nginx 实施了特殊的操作模式,如果代理服务器返回了代码为 101(交换协议)的响应,并且客户端通过以下方式请求协议切换,则允许在客户端与代理服务器之间建立隧道。请求中的“升级”标头。
如上所述,包括“ Upgrade”和“ Connection”的逐跳标头未从客户端传递到代理服务器,因此,为了使代理服务器了解客户端将协议切换到 WebSocket 的意图,这些标头必须明确传递:
复制location /chat/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
复制代码
默认情况下,如果代理服务器在 60 秒内未传输任何数据,则连接将关闭。可以使用 proxy_read_timeout 指令来增加此超时时间 。或者,可以将代理服务器配置为定期发送 WebSocket ping 帧以重置超时并检查连接是否仍然有效。
如果不配置超时时间,隔一会就会断开 具体超时时间具体要根据业务来调整。最终的配置如下:
server{
listen 80;
charset utf-8;
server_name ws.xxx.cn;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 100m;
location / {
proxy_pass http://127.0.0.1:8087;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_connect_timeout 4s;
proxy_read_timeout 7200s;
proxy_send_timeout 12s;
}
}
复制代码
评论