Spring Cloud Gateway 默认不支持 WebSocket 转发,直接配置 ws:// 或 lb:ws:// 才正确;若误用 http:// 或 lb:// 会导致握手后秒断,报 UnsupportedOperationException,根源在于 Netty 升级流程被 NettyRoutingFilter 提前消费请求头,且 globalcors 对 WebSocket 跨域无效,需后端服务自身控制 Origin 校验。

Spring Cloud Gateway 默认不支持 WebSocket 转发,直接配 ws:// 或 lb:ws:// 会连接成功但秒断,报 java.lang.UnsupportedOperationException —— 根本原因是 Netty 的 HTTP/1.1 连接升级流程被过滤器或默认配置中途拦截了。
WebSocket 路由必须用 lb:ws:// 或 ws://,不能用 http:// 或 lb://
网关识别 WebSocket 请求靠的是协议头(Upgrade: websocket)和路径匹配,但底层转发必须走 WebSocket 协议栈。如果写成 http://service-name 或 lb://service-name,Gateway 会当成普通 HTTP 请求处理,握手后无法维持长连接,导致客户端收到 101 状态码后立刻断开。
-
uri: ws://192.168.1.100:8080:适用于单实例、固定 IP/端口的调试场景 -
uri: lb:ws://message-service:生产环境首选,依赖 Nacos/Eureka 注册中心自动发现实例 - 绝对禁止:
uri: http://message-service、uri: lb://message-service—— 这两类配置看似能启动,但 WebSocket 握手后数据帧会被丢弃
必须关闭默认的 NettyRoutingFilter 干预行为
Gateway 内置的 NettyRoutingFilter 会在请求转发前尝试读取完整 body,这对 WebSocket 的 Upgrade 流程是致命的:它会提前 consume 掉 Upgrade header 和 handshake payload,导致后端服务收不到合法握手请求。
- 在
application.yml中显式禁用该 filter:spring: cloud: gateway: default-filters: - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin routes: - id: ws-message uri: lb:ws://message-service predicates: - Path=/ws/** filters: - StripPrefix=1 # 关键:跳过 NettyRoutingFilter 的 body 解析逻辑 - name: RequestRateLimiter args: key-resolver: "#{@ipKeyResolver}" redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 - 更稳妥的做法是自定义一个空的
WebFilter,在NettyRoutingFilter前插入并标记 WebSocket 请求跳过解析(需 Java 配置) - 常见现象:控制台没报错,但浏览器 Network 面板显示 WebSocket 连接状态为
101 Switching Protocols后立即变成closed,就是这个原因
跨域与 CORS 配置不能只靠 globalcors
globalcors 只作用于 HTTP 请求,对 WebSocket 握手阶段的 Origin 校验无效。WebSocket 的跨域控制实际由后端服务决定,网关只能透传 Origin 头,不能替后端做决策。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 网关侧只需确保不删掉
Origin头:filters: - StripPrefix=1 - SetRequestHeader=Origin, "{origin}"(如果前端带了 Origin) - 真正起作用的是后端 WebSocket 服务的配置,例如 Spring Boot 的
setAllowedOrigins("*")或白名单校验逻辑 - 若后端未放开跨域,网关即使配了
allowedOrigins: "*"也无济于事;错误表现是握手返回 403,而不是 101
负载均衡下 Socket.IO 等封装库容易失败
Socket.IO 不是原生 WebSocket,它依赖 HTTP 轮询 + WebSocket 混合降级,并自带心跳、session 绑定等机制。Gateway 的负载均衡默认按请求分发,会导致客户端首次轮询到 A 实例、后续 upgrade 到 B 实例,session 断开。
- 解决方案一:强制 sticky session,Nacos 开启
ip:port作为 instanceId,避免实例复用 - 解决方案二:改用原生 WebSocket 协议(
new WebSocket("ws://...")),绕过 Socket.IO 的复杂握手流程 - 解决方案三:在网关加
CookieSameSite=Strict+ 后端共享 session 存储(如 Redis),但成本高且非银弹 - 验证方式:抓包看
/socket.io/?EIO=...请求是否始终落到同一台后端机器
最易被忽略的一点:WebSocket 路由的 Predicate 必须精确匹配握手路径,比如后端监听 /ws,就绝不能配 Path=/ws/** 后又在 filter 里 StripPrefix=2 —— 这会让实际转发路径变成 /,后端收不到 upgrade 请求。










