WebSocket 建立连接时 426 Upgrade Required 问题
http GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13
如果服务器同意切换,它必须返回状态码 `101 Switching Protocols`,并带有对应的响应头。而 **426 Upgrade Required** 出自 HTTP/1.1 协议,最初的定义是:客户端应当切换到协议清单中列出的某个协议,才能访问所请求的资源。**在 WebSocket 场景中,426 出现意味着服务器或某个中间节点虽然收到了你的升级请求,但它自身没有能力完成协议升级,而是把这个状态码当作一种拒绝方式返回给了客户端。**
---
### 426 错误的主要触发场景
根据实际部署经验,问题极少出在客户端代码,绝大多数都是服务器端或中间代理配置不当造成的。以下是最常见的三个场景。
#### 1. 反向代理或负载均衡器未转发 WebSocket 升级头
这是最高频的原因。如果后端是一个真正的 WebSocket 服务(比如 Node.js、Spring、Go 等),而前端请求是通过 Nginx、Apache 或云负载均衡器(如 AWS ALB、ELB)转发的,这些中间层默认可能不会处理 `Upgrade` 和 `Connection` 头,导致后端收到的只是一个普通 HTTP 请求,从而返回 426 或者直接 400/404。
#### 2. 服务器框架或库没有正确挂载 WebSocket 处理
比如你在 Flask、Express、ASP.NET 等应用里只定义了普通 HTTP 路由,但没有将 `/ws` 这样的路径委托给 WebSocket 处理器。服务器会将它当作一个普通 HTTP 请求,并发现客户端指明了 `Upgrade: websocket`,于是返回 426 表示“请升级,但我这里不支持”。
#### 3. HTTP/2 与 WebSocket 的兼容性问题
HTTP/2 不支持逐跳头中的 `Connection` 字段。如果客户端通过 HTTP/2 发出升级请求,而代理没有正确处理(例如将 HTTP/2 转换成 HTTP/1.1 再传给后端),就可能产生 426。部分浏览器在 HTTP/2 下甚至会直接禁用 WebSocket 握手。
---
### 解决方案一:修复 Nginx 反向代理配置
绝大多数生产环境使用 Nginx 作为 WebSocket 网关。要使升级头正确传递,必须在 `location` 块中显式设置以下指令:
```nginx
location /ws/ {
proxy_pass http://backend_server;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400s; # 防止长连接被意外切断
}
配置要点解释:
- proxy_http_version 1.1:HTTP/1.1 才支持分块传输和协议升级。
- proxy_set_header Upgrade $http_upgrade:将客户端发来的
Upgrade头原样传给后端。 - proxy_set_header Connection "upgrade":硬编码为 "upgrade",而不是使用变量
$connection_upgrade,因为某些版本 Nginx 中该变量可能为空。 - 防止超时断开:
proxy_read_timeout设长一些(单位秒),因为 WebSocket 连接可能长时间没有数据。
配置完成后务必执行 nginx -t && nginx -s reload。
解决方案二:Apache httpd 的 mod_proxy_wstunnel
如果负载均衡是 Apache,需要使用 mod_proxy_wstunnel 模块(确保已启用)。在虚拟主机或位置配置中添加类似规则:
ProxyPass "/ws/" "ws://backend:8080/"
ProxyPassReverse "/ws/" "ws://backend:8080/"
对于 WSS(加密连接),后端地址改为 wss://,且确保 SSL 终结后的请求头正确传递。同时注意 Apache 版本需要支持此模块,一般 2.4.5 以上版本内置。
解决方案三:IIS / ARR (Application Request Routing) 配置
微软的 ARR 3.0 和 URL 重写模块可用于 WebSocket 反向代理。需要在 ARR 层面关闭“响应缓冲”(Response Buffering),否则 WebSocket 帧会被阻塞。
关键步骤:
- 在 IIS 管理器中,选择服务器级或站点级的“Application Request Routing Cache”。
- 点击右侧操作面板的“Server Proxy Settings”。
- 将“Response buffer threshold (KB)”设置为 0,以禁用缓冲。
- 同时确保 URL 重写规则将流量转发给后端 WebSocket 服务。
此外,后端应用程序池必须启用 WebSocket 支持(在 IIS 的“WebSocket”功能中打开)。
解决方案四:检查后端应用代码的挂载方式
不同语言和框架开启 WebSocket 的方式也需要一一排查。
Node.js (Express + ws 库)
最常见的错误是忘记将 HTTP 服务器的 upgrade 事件交给 ws 处理。
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
server.on('upgrade', (request, socket, head) => {
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request);
});
});
Spring Boot (Java)
必须使用 @EnableWebSocket 并实现 WebSocketConfigurer,或者使用 STOMP 子协议。如果只是添加了普通 @Controller,没有注册 WebSocket 端点,请求就会以 426 结束。
Go (gorilla/websocket)
在 HTTP 处理器中必须调用 Upgrader.Upgrade(w, r, nil) 并检查返回的错误,而不是仅在路由里返回普通响应。
var upgrader = websocket.Upgrader{}
func wsHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
defer conn.Close()
// ... 读写消息
}