spring cloud gateway需显式配置websocket路由,uri必须用ws://或lb:ws://协议前缀,且需透传sec-websocket-key等关键header,路径断言应兼容查询参数,负载均衡下需确保会话粘性。

Spring Cloud Gateway 默认不自动处理 WebSocket 请求,必须显式配置路由和过滤器才能完成代理转发。如果你的前端连不上后端 WebSocket 服务,大概率不是后端没写好,而是网关拦住了升级请求。
WebSocket 路由必须用 ws:// 或 lb:ws:// 协议前缀
WebsocketRoutingFilter 只识别以 ws://、wss:// 或 lb:ws://、lb:wss:// 开头的 uri。用 http:// 或 lb://(缺 ws)会导致请求被当成普通 HTTP 转发,握手失败,浏览器报错 Unexpected response code: 200 或直接断开。
-
✅ 正确写法:
uri: ws://192.168.1.100:8080/websocket
uri: lb:ws://message-service
-
❌ 错误写法:
uri: http://192.168.1.100:8080/websocket
uri: lb://message-service
注意:lb:ws:// 要求服务已注册到 Nacos/Eureka,且服务实例实际提供的是 WebSocket 端点(如 /websocket),不是 HTTP 接口。
跨域失败?别只配 CorsConfiguration,网关层要透传关键 Header
WebSocket 握手是 HTTP 请求,但涉及多个特殊 Header,比如:
Sec-WebSocket-KeySec-WebSocket-VersionConnection: UpgradeUpgrade: websocket
如果网关过滤器(如自定义的 CorsWebFilter)或全局配置清除了这些 Header,握手就会被后端拒绝,报错 Invalid request: missing Sec-WebSocket-Key。
- 必须确保网关不 strip 这些字段:
- 不要在
GlobalFilter中调用exchange.getRequest().mutate().headers(...)覆盖原始 headers - 如果用了
allowedOrigins: ["*"],在生产环境要改成具体域名,否则某些浏览器会拒绝带凭证的 WebSocket 连接 - 若后端是 Spring Boot 的
@ServerEndpoint实现(非 STOMP),它不解析 Origin,但网关仍需透传,否则某些反向代理(如 Nginx)会拦截
- 不要在
示例安全的跨域放行(仅限开发):
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowed-origins: "http://localhost:3000"
allowed-methods: "*"
allowed-headers: "*"
allow-credentials: true
Path 断言要匹配完整握手路径,不能漏掉查询参数
WebSocket 客户端连接时 URL 常带参数,例如:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
ws://gateway:8080/ws?token=abc&uid=1001
而你的路由只写了:
- Path=/ws/**
这个断言能匹配路径,但不保证查询参数透传——默认是透传的,但一旦你加了 StripPrefix 过滤器,就可能把整个 path 截断,导致后端收不到 token。
- 如果后端依赖 query 参数鉴权,不要用
StripPrefix=1 - 更稳妥的方式是显式保留:
filters: - StripPrefix=0
- 或者改用更精确的断言(推荐):
- Path=/ws
这样既匹配/ws,也兼容/ws?...,避免路径截断引发的参数丢失
负载均衡下 WebSocket 连接不稳定?检查心跳与粘性会话
lb:ws://service-name 走的是 Ribbon 或 Spring Cloud LoadBalancer,默认轮询。但 WebSocket 是长连接,一次握手建立后,后续帧应始终打到同一实例,否则会因 session 不存在而断开。
-
Netty + Socket.IO 类服务:自带心跳,但网关层若超时关闭空闲连接,就会中断。需调大:
spring: cloud: gateway: httpclient: pool: max-idle-time: 300000 # 5分钟 max-life-time: 600000 # 10分钟 connect-timeout: 5000 response-timeout: 60s 若服务本身不支持共享 session(如纯
@ServerEndpoint),必须启用 sticky session,Nacos 不支持,得换用 Kubernetes Service + SessionAffinity,或在网关层用Weight+ 固定 IP 路由(见知识库中多节点直连写法)
最易被忽略的一点:WebSocket 连接建立后,网关不会主动刷新连接池中的 channel,旧连接可能复用已关闭的底层 TCP,建议配合健康检查 + 主动 close idle connection。
真实项目里,WebSocket 转发失败十次有八次是因为 uri 写成 http 或漏了 ws,剩下两次是 header 被过滤或路径断言太窄。先盯死这三处,比调一堆过滤器更快定位问题。










