代理 WebSocket 连接,意味着配置一台中间服务器正确处理 HTTP Upgrade 握手,并维持 WebSocket 通信所需的持久、全双工 TCP 连接。这种配置可为实时应用提供负载均衡、SSL 终止、缓存和访问控制等能力。
理解 WebSocket 代理
WebSocket 在单条 TCP 连接上,于客户端与服务器之间建立持久连接。这与通常采用短生命周期请求/响应循环的传统 HTTP 不同。从 HTTP 切换到 WebSocket,是通过 HTTP/1.1 协议中的"Upgrade"机制发起的。
客户端会发送一个带有特定头部的初始 HTTP GET 请求:
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Key: [base64 编码的 nonce]
* Sec-WebSocket-Version: 13
如果服务器支持 WebSocket,会返回 HTTP 101 Switching Protocols 状态,确认升级:
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: [由客户端密钥派生出的响应密钥]
握手完成后,该连接不再是 HTTP,而是一条裸 TCP 套接字,WebSocket 帧在其上交换。代理服务器必须配置为正确转发这些 Upgrade 与 Connection 头部,并随后维持这条长连接,既不缓冲也不提前关闭。
常见代理服务器的配置
Nginx
Nginx 通过处理 HTTP Upgrade 握手,可以有效代理 WebSocket 连接。关键在于把客户端的 Upgrade 和 Connection 头部传给后端服务器,并确保使用 HTTP/1.1。
http {
upstream websocket_backend {
server backend_server_1:8080;
server backend_server_2:8080;
}
server {
listen 80;
server_name yourdomain.com;
location /ws/ {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host; # 如果后端依赖 host 头,这一项很重要
proxy_read_timeout 86400s; # 针对长连接按需调整
proxy_send_timeout 86400s;
proxy_buffering off; # 关闭缓冲以传输实时数据
}
# 其他 HTTP location
location / {
proxy_pass http://other_http_backend;
# ... 其他 HTTP 代理设置
}
}
}
proxy_http_version 1.1;:Upgrade头部机制必需。proxy_set_header Upgrade $http_upgrade;:转发客户端的Upgrade头部。proxy_set_header Connection "upgrade";:转发客户端的Connection头部。若未显式设为 "upgrade",Nginx 会在 HTTP/1.1 连接中自动把Connection: upgrade转换为Connection: keep-alive。显式设置可确保 WebSocket 行为正确。proxy_read_timeout/proxy_send_timeout:把默认值调高,防止 Nginx 因空闲而关闭长连接的 WebSocket。proxy_buffering off;:阻止 Nginx 缓冲响应,这对实时 WebSocket 通信保持最小延迟至关重要。
Apache HTTP Server
Apache 可通过 mod_proxy 的组成部分 mod_proxy_wstunnel 代理 WebSocket 连接。请确保已启用 mod_proxy、mod_proxy_http 和 mod_proxy_wstunnel。
<VirtualHost *:80>
ServerName yourdomain.com
# 启用代理模块
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
# 代理 WebSocket 连接
<Location /ws/>
ProxyPass ws://backend_server:8080/ws/
ProxyPassReverse ws://backend_server:8080/ws/
</Location>
# 代理安全 WebSocket 连接(WSS)
<Location /wss/>
ProxyPass wss://backend_server:8443/wss/
ProxyPassReverse wss://backend_server:8443/wss/
</Location>
# 其他 HTTP location
<Location />
ProxyPass http://other_http_backend/
ProxyPassReverse http://other_http_backend/
</Location>
</VirtualHost>
ProxyPass ws://...:ws://方案告诉mod_proxy_wstunnel处理 WebSocket 升级。安全 WebSocket 请使用wss://。ProxyPassReverse:确保响应头中的 URL 被正确重写。- 当指定
ws://或wss://时,Apache 的mod_proxy会自动处理Upgrade和Connection头部。
HAProxy
HAProxy 自 1.5 版起支持 WebSocket 代理。它可以运行在 http 模式下检查头部,或在 tcp 模式下做裸转发。对 WebSocket 而言,常见做法是使用 http 模式并配以检测升级请求的专门规则。
global
log /dev/log local0 info
maxconn 4000
user haproxy
group haproxy
daemon
defaults
mode http
log global
option httplog
option dontlognull
timeout connect 5000ms
timeout client 50000ms # 为客户端侧 WebSocket 连接调高
timeout server 50000ms # 为后端侧 WebSocket 连接调高
frontend http_frontend
bind *:80
acl is_websocket hdr(Upgrade) -i websocket
use_backend ws_backend if is_websocket
default_backend http_backend
backend ws_backend
mode http
# 该选项对 HTTP 模式下的 WebSocket 至关重要;它让 HAProxy 在 HTTP
# 升级完成后切换为 TCP 隧道。
option http-tunnel
server backend_ws_1 backend_server_1:8080 check
server backend_ws_2 backend_server_2:8080 check
backend http_backend
mode http
server backend_http_1 backend_server_3:80 check
acl is_websocket hdr(Upgrade) -i websocket:定义一个 Access Control List(ACL),匹配包含Upgrade: websocket头部的请求(不区分大小写)。use_backend ws_backend if is_websocket:把 WebSocket 升级请求导向ws_backend。option http-tunnel:这是关键。当 HAProxy 检测到Upgrade头且启用该选项时,会在初始 HTTP 握手之后从 HTTP 解析模式切换为裸 TCP 隧道,从而让 WebSocket 协议畅通运行。timeout client/timeout server:对 WebSocket 后端应大幅调高这些超时,避免 HAProxy 关闭长连接。
常见问题排查
连接掉线或超时
- 代理超时:最常见的原因。请确保
proxy_read_timeout、proxy_send_timeout(Nginx)、timeout client、timeout server(HAProxy)或其他代理中的等效设置对 WebSocket 连接足够大(例如 24 小时以上)。 - 空闲连接:部分代理或网络设备会激进地关闭空闲 TCP 连接。若 WebSocket 协议带有保活机制(如 ping/pong 帧),请在客户端和服务器两侧都配置为定期发送 ping。
- 负载均衡器空闲超时:若代理前方还有负载均衡器,它也可能有自己的空闲超时。请检查这些设置。
HTTP 400 Bad Request(Upgrade 头缺失或不正确)
- 客户端请求:确认客户端正确发送了
Upgrade: websocket和Connection: Upgrade头部。 - 代理配置:确保代理已配置为把这些头部转发给后端。
- Nginx:检查
proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade";。 - Apache:确保已启用
mod_proxy_wstunnel,且ProxyPass使用ws://或wss://。 - HAProxy:确认
acl is_websocket正确识别了Upgrade头,且use_backend将其导向设置了option http-tunnel的后端。
- Nginx:检查
HTTP 502 Bad Gateway
- 后端服务器问题:代理从后端收到了无效响应。
- 后端 WebSocket 服务器是否在运行并监听预期端口?
- 后端是否已正确配置以处理 WebSocket 升级?
- 查看后端服务器日志中的错误。
- 网络连通性:确认代理能通过指定端口访问后端服务器。
- 代理缓冲:虽然
proxy_buffering off通常有利于 WebSocket,但数据量过大或配置有误时,代理若难以处理数据流,有时也会导致 502。
安全注意事项(SSL/TLS 终止)
- WSS(安全 WebSocket):对于安全 WebSocket 连接(wss://),必须由代理完成 SSL/TLS 终止。
- 代理监听 443 端口(或其他安全端口),完成 TLS 握手,然后把解密后的 WebSocket 流量转发给后端(内部通常走普通的
ws://或http://)。 - WSS 的 Nginx 示例:
```nginx
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /etc/nginx/ssl/yourdomain.crt;
ssl_certificate_key /etc/nginx/ssl/yourdomain.key;location /ws/ { proxy_pass http://websocket_backend; # 后端可以是普通 HTTP 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_send_timeout 86400s; proxy_buffering off; }}
`` * **Origin 头:**WebSocket 客户端会发送Origin` 头。后端服务器出于安全考虑常常校验该头部。若后端依赖它,请确保代理没有剥离或修改该头部。
- 代理监听 443 端口(或其他安全端口),完成 TLS 握手,然后把解密后的 WebSocket 流量转发给后端(内部通常走普通的
代理缓冲区限制
- Nginx:
proxy_buffering off;至关重要。若不设置,Nginx 可能缓冲较大的 WebSocket 消息,带来延迟或引发超时。 - HAProxy:
option http-tunnel通过切换到裸 TCP 解决此问题。
面向 WebSocket 的代理服务器对比
| 特性 | Nginx | Apache HTTP Server (mod_proxy_wstunnel) | HAProxy |
|---|---|---|---|
| 配置方式 | 手动转发头部 | 专用 ws:// / wss:// 指令 |
ACL 与 option http-tunnel |
| 复杂度 | 中等 | 中等 | 中到高(涉及高级功能时) |
| 性能 | 高(事件驱动) | 中等(基于进程/线程) | 高(事件驱动) |
| SSL/TLS 终止 | 出色 | 良好 | 出色 |
| 负载均衡 | 轮询、IP hash、least_conn | 轮询 | 丰富(多种算法、健康检查) |
| 会话保持 | IP hash(基础)、第三方模块 | 有限 | 基于 cookie、源 IP |
| 缓冲控制 | proxy_buffering off |
由 mod_proxy_wstunnel 处理 |
option http-tunnel 关闭 HTTP 缓冲 |
关键注意事项
- 会话保持:某些 WebSocket 应用中,客户端需要在整个会话期间连到同一台后端服务器(例如服务端保存了状态)。
- Nginx:在
upstream块中使用ip_hash。 - HAProxy:使用
balance source或cookie指令。 - 条件允许时,请把后端应用设计为无需会话保持即可横向扩展,这会简化负载均衡。
- Nginx:在
- 健康检查:在代理中为后端 WebSocket 服务器配置可靠的健康检查,确保只有健康的服务器接收 WebSocket 升级请求。
- 日志:在代理和后端配置完善的日志。WebSocket 连接问题可能是瞬时的,详细日志对诊断必不可少。
- HTTP/2 与 WebSocket:尽管 WebSocket 的升级握手通常使用 HTTP/1.1,但它可以与 HTTP/2 共存。不过 WebSocket 协议本身并不直接跑在 HTTP/2 帧之上。处理来自客户端的 HTTP/2 的代理,在向后端发起 WebSocket 升级时通常会降级为 HTTP/1.1。
