Spring Cloud Gateway 和 Nginx 网关代理 WebSocket 路由配置

最近有个小程序项目客户在并网时有个需求:需要在他们的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 GatewayNginx)的配置缺失网络环境干扰SSL/TLS 证书问题。下表汇总了典型错误现象、可能原因及对应解决方案,快速定位问题。

错误现象可能原因解决方案
1002 (PROTOCOL_ERROR)
协议错误
1. 反向代理未转发升级头:Nginx/网关未显式传递 UpgradeConnection 头。
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. 缺少必要请求头:服务端强制要求 OriginHost 等头部,但客户端未提供。
3. 路径匹配错误:代理转发时路径重写(StripPrefix)导致后端无法识别路由。
4. 请求体过大(非标准):某些实现会限制握手请求的头部大小。
1. 检查并正确编码 URL:对查询参数进行 urlencode
2. 手动添加头部:使用 -H "Origin: https://example.com"-H "Host: ..."
3. 调整路径重写策略:在网关中谨慎使用 StripPrefix,确保转发后的路径与后端路由匹配。
4. 查看后端日志:定位具体拒绝原因。
426 (Upgrade Required)
需要升级
1. 代理未传递 Upgrade:Nginx/网关默认不转发 UpgradeConnection 头。
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 -kcurl --insecure
2. 指定 CA 证书websocat --ca /path/to/ca.pem
3. 调整服务端 TLS 配置:放宽 ssl_protocolsssl_ciphers
4. Nginx 代理关闭后端验证proxy_ssl_verify off;(或配置正确的 CA)。
连接建立后立即断开
(无显式错误)
1. 空闲超时:代理/负载均衡器的空闲超时(默认 60s)太短。
2. 心跳缺失:未发送 Ping/Pong 保持连接。
3. 后端应用逻辑主动关闭(如认证过期)。
1. 增大代理超时(如上)。
2. 定期发送 Ping 帧:客户端设置 --ping-interval,或应用层心跳。
3. 检查后端日志,确认是否有业务层面关闭原因。


参考:

anzhihe 安志合个人博客,版权所有 丨 如未注明,均为原创 丨 转载请注明转自:https://chegva.com/6755.html | ☆★★每天进步一点点,加油!★★☆ | 

您可能还感兴趣的文章!

发表评论

电子邮件地址不会被公开。 必填项已用*标注