最近有个小程序项目客户在并网时有个需求:需要在他们的Spring Cloud Gateway 公网网关开个入口,打到自建的Nginx代理转发到后端服务,需要同时能支持 http 和 ws 的请求。整体访问链路从公网 → Spring Cloud Gateway → Nginx → 应用服务。主要的工作是Spring Cloud Gateway 和 Nginx 的路由配置和整个链路的联调,mark一下。
一、Spring Cloud Gateway 配置(第一层)
spring: cloud: gateway: routes: # 1. WebSocket 路由:处理 WebSocket 握手请求 - id: websocket_route uri: wss://your-backend-service # 非加密的 WebSocket 协议使用 ws:// predicates: # 匹配 /ws/ 路径及其子路径,并确保包含 Upgrade 头 - Path=/ws/** - Header=Upgrade, websocket filters: # 不要用 StripPrefix,会破坏 WebSocket 握手头 - name: SetResponseHeader args: name: Sec-WebSocket-Accept value: ".*" # 让后端自行生成 # 保持 Host 头不变 - name: PreserveHostHeader # 2. HTTP 路由:处理普通的 HTTP 请求处理(处理非 WebSocket 请求) - id: http_route # 普通的 HTTP 负载均衡 uri: lb://your-backend-service predicates: # 匹配相同的路径 - Path=/ws/** filters: # 显式保证查询参数透传 - StripPrefix=0 # 全局 CORS 配置(可选) globalcors: cors-configurations: '[/**]': allowed-origins: "*" allowed-methods: "*" allowed-headers: "*" allow-credentials: true
二、Nginx 配置(第二层)
http {
# 定义 connection_upgrade 变量
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream app_cluster {
server 127.0.0.1:8080;
# 可添加多个 app 实例
}
# ---------- HTTP 服务器(提供 ws://)----------
server {
listen 80;
server_name test.com.cn; # 内网域名或 IP
location /ws/ {
# 去掉 /ws/ 前缀,将请求转发到后端根路径
proxy_pass http://app_cluster/;
# 必须的 WebSocket 握手配置
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 传递原始 Host 和客户端 IP
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 超时设置(长连接)
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
}
# 其他非 /ws/ 请求
location / {
proxy_pass http://app_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
# ---------- HTTPS 服务器(提供 wss://)----------
server {
listen 443 ssl;
server_name test.com.cn;
# 网关自身的 SSL 证书(客户端信任的证书)
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location /ws/ {
proxy_pass http://app_cluster/; # 注意末尾斜杠,可去掉 /ws/ 前缀,不加末尾"/" 会保留 /ws/ 前缀
# 关键:WebSocket 握手依赖 HTTP/1.1
proxy_http_version 1.1;
# 传递 WebSocket 升级头
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 传递原始信息
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; # 告诉后端客户端实际使用的是 HTTPS(当后端需要区分协议时有用)
# 长连接超时(避免空闲断开)
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
# 关闭缓冲,提升实时性
proxy_buffering off;
}
# 其他非 /ws/ 请求
location / {
proxy_pass http://app_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}后端为 HTTPS(WSS,加密)的情况
如果后端 WebSocket 服务器也要求使用 WSS(例如 https://backend_server:8443),则需额外处理 SSL 验证:
location /ws/ {
proxy_pass https://backend_server:8443/; # 使用 HTTPS 协议
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 如果后端使用自签名证书,关闭 SSL 验证(测试环境)
proxy_ssl_verify off;
# 或者指定 CA 证书链:proxy_ssl_trusted_certificate /path/to/ca.pem;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
}
# 强制将 HTTP 重定向到 HTTPS(可选配置)
server {
listen 80;
server_name test.com.cn;
return 301 https://$host$request_uri;
}关键检查项:
确认
proxy_pass的 URL 正确:如果你想去掉/ws/前缀,proxy_pass末尾要带/,如proxy_pass http://backend/;。确认后端能接收未加密流量:如果网关 Nginx 处理了 HTTPS(WSS),那么它转发给下游 Nginx 的可以是 HTTP(WS)。请确保你的下游 Nginx 和后端服务能正确处理这种转发。
启用会话保持 (Sticky Sessions):对于有状态的应用,应在目标组(Target Group)上启用会话保持,确保来自同一客户端的请求始终到达同一后端。
检查安全组:确保 LB 的安全组允许来自客户端和去往后端的目标端口(如 80/443)的流量。
三、验证步骤
1. 检查 Gateway 和 Nginx 路由是否生效
curl -v -H "Host: test.com.cn" \ -H "Connection: Upgrade" \ -H "Upgrade: websocket" \ -H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \ -H "Sec-WebSocket-Version: 13" \ https://网关IP:网关端口/ws/channel/checkHost?id=xxx
预期返回 101 Switching Protocols。
使用 websocat 工具测试:brew install websocat(mac安装)
使用
websocat ws://test.com.cn/ws/测试 WS。使用
websocat -k wss://test.com.cn/ws/测试 WSS(自签名证书需加-k忽略验证)。在线验证:http://tool0.com/websocket/
测试普通 HTTP 请求:
curl -v https://test.com.cn/ws/channel/checkHost?id=xxx
应正常返回业务数据。
2. 查看 Gateway 和 Nginx 日志
在 application.yml 中为 org.springframework.cloud.gateway 开启 DEBUG 级别日志,观察请求被匹配到了哪条路由。
logging: level: org.springframework.cloud.gateway: DEBUG org.springframework.web: DEBUG
观察请求被哪个路由匹配。
查看 Nginx 错误日志:
tail -f /var/log/nginx/error.log,可定位连接后端失败或 SSL 错误。tail -f /var/log/nginx/access.log,可观察请求被哪个路由匹配。
3. 分阶段验证
先验证后端服务是否正常,然后排查 Nginx 配置,再访问 Gateway 服务(绕过 Nginx),确认 Gateway 配置。
测试验证后端服务 ws 连接是否正常
再通过 Nginx 访问:
https://test.com.cn/ws/...访问 Gateway(绕过 Nginx):
https://网关IP:端口/ws/...
四、常见错误及处理
在配置 WebSocket 代理或客户端时,最常见的错误往往源于反向代理(如 Spring Cloud Gateway、Nginx)的配置缺失、网络环境干扰或 SSL/TLS 证书问题。下表汇总了典型错误现象、可能原因及对应解决方案,快速定位问题。
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 1002 (PROTOCOL_ERROR) 协议错误 | 1. 反向代理未转发升级头:Nginx/网关未显式传递 Upgrade 和 Connection 头。2. HTTP 版本过低:代理使用 HTTP/1.0 与后端通信,而 WebSocket 要求 HTTP/1.1+。 3. 子协议协商失败:客户端请求的 Sec-WebSocket-Protocol 服务端不支持。4. 数据帧格式错误:客户端发送了非掩码帧(仅服务端可发送非掩码帧)。 | 1. 配置反向代理: - Nginx: proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";- Spring Cloud Gateway:路由 uri 使用 ws:// 或 lb:ws://,避免使用 StripPrefix。2. 指定子协议:在客户端使用 --protocol 参数或设置 Sec-WebSocket-Protocol 头。3. 检查客户端库:确认发送的数据符合 RFC 6455(如客户端帧必须掩码)。 |
| 400 (Bad Request) 错误请求 | 1. URL 路径/参数编码错误:包含非法字符(如未转义的 [、]、空格)。2. 缺少必要请求头:服务端强制要求 Origin、Host 等头部,但客户端未提供。3. 路径匹配错误:代理转发时路径重写( StripPrefix)导致后端无法识别路由。4. 请求体过大(非标准):某些实现会限制握手请求的头部大小。 | 1. 检查并正确编码 URL:对查询参数进行 urlencode。2. 手动添加头部:使用 -H "Origin: https://example.com" 或 -H "Host: ..."。3. 调整路径重写策略:在网关中谨慎使用 StripPrefix,确保转发后的路径与后端路由匹配。4. 查看后端日志:定位具体拒绝原因。 |
| 426 (Upgrade Required) 需要升级 | 1. 代理未传递 Upgrade 头:Nginx/网关默认不转发 Upgrade 和 Connection 头。2. 后端强制要求 TLS:服务端只接受 wss:// 连接,客户端却使用 ws://。3. 客户端协议与服务器期望不符:例如网关用 http:// 而非 ws:// 转发。 | 1. 确保代理配置正确(同上 1002 的 Nginx/网关配置)。 2. 使用正确的协议前缀:客户端使用 wss://,代理转发时用 ws://(若后端非加密)或 wss://(若后端加密)。3. 检查 Spring Cloud Gateway 路由: uri 必须以 ws:// 或 lb:ws:// 开头。 |
| I/O failure 输入/输出错误 | 1. 网络不可达:目标 IP/端口被防火墙拦截,或服务未启动。 2. DNS 解析问题: localhost 同时解析 IPv4 和 IPv6,旧版 websocat 只尝试第一个。3. 连接超时:代理或服务端空闲超时断开连接。 4. 代理/负载均衡器主动断开:健康检查失败或会话粘性未配置。 | 1. 使用具体 IP 替代主机名(如 127.0.0.1)。2. 升级 websocat 至 ≥v1.13(支持并发连接多 IP)。3. 增加超时时间: - Nginx: proxy_read_timeout 3600s; proxy_connect_timeout 60s;- 网关:配置 spring.cloud.gateway.httpclient.connect-timeout 等。4. 启用会话保持:负载均衡器配置 Sticky Session。 |
| SSL handshake failed SSL 握手失败 | 1. 证书不受信任:服务端使用自签名证书或内部 CA 证书,客户端系统不信任。 2. 证书域名与请求域名不匹配:证书 CN/SAN 不包含访问的域名。 3. TLS 版本/加密套件不兼容:服务端强制 TLS 1.3,客户端仅支持 TLS 1.2。 4. 代理 SSL 验证问题:Nginx 向后端转发时启用 proxy_ssl_verify 但未配置 CA。 | 1. 跳过验证(仅测试):websocat -k 或 curl --insecure。2. 指定 CA 证书: websocat --ca /path/to/ca.pem。3. 调整服务端 TLS 配置:放宽 ssl_protocols 和 ssl_ciphers。4. Nginx 代理关闭后端验证: proxy_ssl_verify off;(或配置正确的 CA)。 |
| 连接建立后立即断开 (无显式错误) | 1. 空闲超时:代理/负载均衡器的空闲超时(默认 60s)太短。 2. 心跳缺失:未发送 Ping/Pong 保持连接。 3. 后端应用逻辑主动关闭(如认证过期)。 | 1. 增大代理超时(如上)。 2. 定期发送 Ping 帧:客户端设置 --ping-interval,或应用层心跳。3. 检查后端日志,确认是否有业务层面关闭原因。 |
参考: