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;
}
}