使用 Spring Cloud Gateway 替换 Zuul 实现接入 WebSocket 教程

本文介绍如何使用 Spring Cloud Gateway 替换 Zuul 来支持 WebSocket。Spring Cloud Gateway 具有更好的 WebSocket 支持,文中详细阐述了从引入依赖、配置文件设置到解决WebSocket连接中出现的问题,包括路径匹配、SockJS、Stomp 的作用以及如何处理跨域问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

之前的项目使用的是Zuul网关,有个需求需要用到WebSocket,所以一直在查Spring Cloud Zuul 转发 WebSocket请求的教程和文章,查来查去,发现不行,Zuul对WebSocket的支持不是很友好。

总结下来就是以下几点:

  • 高版本的websocket在第一次http请求后,使用的是更快速的tcp连接,zuul网关只能管理http请求,并且不支持tcp以及udp请求
  • zuul转发websocket时,会将websocket降级为http请求转发掉(轮询的方式,效率不是很理想),换句话说就是不支持转发长连接,zuul2好像可以。

这让我很发愁,毕竟我是一个强迫症,做自己的项目,当然不能凑活,于是我就找其他办法,在查的偶然间,我发现他们说Spring Cloud Gateway 支持转发WebSocket,我眼睛一亮,但是换网关,也不能说换就换,就搜了搜能不能在Zuul的基础上想想办法,网上的办法有很多,但可惜我觉得都不理想,没有办法,换Gateway吧,话不多说,上教程。

第一步,引入Gateway的 maven依赖

	<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

在这里要说个坑(敲黑板,划重点了)由于Gateway自带Netty,和tomcat冲突,所以看上面,第二个依赖,spring-boot-starter-websocket 这个里面自带spring-boot-starter-web jar包,而spring-boot-starter-web jar包里面包含tomcat一系列jar包,所以这里要给排除掉,不然启动会报错,如果依然报错,看看报错提示,是不是提示netty和tomcat冲突,在找找其他jar包里有没有包含tomcat的,排除掉。

第二步,依赖引入完毕,那么开始写配置文件

Gateway 有两种方式,一种是配置文件,一种是拦截器
下面是yml文件的配置

spring:
  cloud:
    #spring-cloud-gateway
    gateway:
      routes:
      - id: xxx              #路由的id,参数配置不要重复,如不配置,Gateway会使用生成一个uuid代替。
        uri: lb://xxx        #lb:// 表示从注册中心获取路径进行转发,xxx是注册在注册中心的微服务的名称
        predicates:
        - Path=/xxx/**       #符合该路径后,转发
      #WebSocket转发配置
      - id: xxx     
        uri: lb:ws://xxx     #lb:ws://xxx 表示从注册中心获取路径转发,并且请求协议换成ws	
        predicates:
        - Path=/xxx/**

好了,配置文件写好了,- Path=/xxx/** 设置好断言,如果你的请求路径符合这个规则,Gateway就会进行相应的转发,是不是很简单,但是这里有个问题,普通的转发无所谓,到这里就结束了,可是WebSocket的转发,会有点问题,我使用的是SockJS + Stomp + WebSocket,在这里就要简单介绍一下 SockJS、Stomp、WebSocket了。

WebSocket
  • WebSocke是 HTML5 提供的一种在单个 TCP 连接上进行全双工通讯的协议。

  • WebSocket协议是基于TCP的一种新的网络协议,是一个应用层协议,是TCP/IP协议的子集。

  • 它实现了浏览器与服务器全双工(full-duplex)通信,客户端和服务器都可以向对方主动发送和接收数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

  • 在JS中创建WebSocket后,会有一个HTTP请求从浏览器发向服务器。在取得服务器响应后,建立的连接会使用HTTP升级将HTTP协议转换为WebSocket协议。也就是说,使用标准的HTTP协议无法实现WebSocket,只有支持那些协议的专门浏览器才能正常工作。由于WebScoket使用了自定义协议,所以URL与HTTP协议略有不同。未加密的连接为ws://,而不是http://。加密的连接为wss://,而不是https://,所以如果你的项目使用了网关,又想使用WebSocket,在网关转发这方面,就会遇到问题。

SockJS
  • SockJS是一个浏览器JavaScript库,它提供了一个连贯的、跨浏览器的JavaScript API,在浏览器和web服务器之间建立一个低延迟、全双工、跨域通信通道。SockJs的一大好处是提供了浏览器兼容性,优先使用原生的WebSocket,在不支持websocket的浏览器中,会自动降为轮询的方式。
Stomp
  • STOMP(Simple Text-Orientated Messaging Protocol),中文为:面向消息的简单文本协议

  • websocket定义了两种传输信息类型:文本信息和二进制信息。类型虽然被确定,但是他们的传输体是没有规定的。所以,需要用一种简单的文本传输类型来规定传输内容,它可以作为通讯中的文本传输协议。

  • STOMP是基于帧的协议,客户端和服务器使用STOMP帧流通讯

  • 一个STOMP客户端是一个可以以两种模式运行的用户代理,可能是同时运行两种模式。

  • 作为生产者,通过SEND框架将消息发送给服务器的某个服务

  • 作为消费者,通过SUBSCRIBE制定一个目标服务,通过MESSAGE框架,从服务器接收消息。

总结
  • SockJS 提供了浏览器兼容性,在不支持WebSocket的浏览器中,会自动降为轮询的方式。
  • Stomp 简单理解就是规定了传输内容,作为通讯中的文本传输协议;这个我也是看的一知半解,但是我觉得如果你对WebSocket了解足够多的话,你就清楚为什么要用它。
  • WebSocket 实现了浏览器与服务器全双工通信,客户端和服务器都可以向对方主动发送和接收数据,而且第一次建立WebSocket的连接的协议是HTTP或HTTPS协议,url使用的是http://或https://,建立成功之后,url使用的是ws://或wss://。

看到这里,你大概就明白了,我说的问题在哪里了(划重点)
由于WebSocket第一次连接使用的是http协议或者是https协议,并且咱们的配置文件还是这样配置的:

      - id: xxx     
        uri: lb:ws://xxx     #lb:ws://xxx 表示从注册中心获取路径转发,并且请求协议换成ws	
        predicates:
        - Path=/xxx/**

这样配置的意思就是,如果符合 - Path=/xxx/** 这个地址路径,那么就会转换成WebSocket协议来进行转发请求。

如果你的项目没有网关,前端的WebSocket建立请求会直接请求到你的WebSocket服务上,成功建立连接。

如果你的项目有网关,而且你还这样配置了转发规则,那么前端的每一次WebSocket请求都会以url是ws://或wss://这个路径进行转发请求,连接就不会建立成功。

那么这个时候就要做一下特殊处理,话不多说,上代码。

第三步,拦截第一次WebSocket请求,做特殊处理。

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import 
<think>嗯,用户想了解如何通过Spring Cloud GatewayZuul实现基于规则的路由转发。我需要先回顾一下这两个组件的相关知识。根据引用内容,Spring Cloud Gateway是基于Spring WebFlux和Netty的,支持异步非阻塞模型,而Zuul 1.x是同步阻塞的。现在可能更多推荐使用Gateway,但用户可能还是需要两者的实现方法。 首先,用户需要的是动态路由转发,也就是根据特定规则(比如请求头、路径、参数等)来动态决定路由的目标。引用中提到Gateway可以通过配置文件或拦截实现,但动态路由可能需要结合数据库或者配置中心来实现动态更新。 对于Spring Cloud Gateway的动态路由,我记得可以通过RouteLocator Bean来编程式定义路由规则。例如,根据请求头中的版本信息路由到不同的服务实例。引用中的配置示例用的是YAML,但动态的话可能需要结合数据库,比如从数据库读取路由规则并注册到RouteLocator中。 关于Zuul的动态路由,Zuul 1.x本身可能没有内置的动态路由支持,但可以通过自定义Filter来实现,比如从数据库加载路由规则,并在过滤器中修改请求的转发路径。不过Zuul 2.x是异步的,但可能用户还在用1.x版本,所以需要说明基于Zuul 1.x的实现方法。 用户可能需要具体的步骤,比如如何编写配置、如何集成配置中心,或者如何通过代码动态更新路由。比如,在Gateway中,使用Actuator端点来刷新路由,或者监听配置中心的变化事件来重新加载路由。 另外,引用3中的配置文件示例使用了predicates,比如Path和Header,这些可以用来定义路由规则。动态路由可能需要将这些规则存储在外部,并能够实时更新,而不需要重启网关服务。 可能还需要考虑权限验证,比如在过滤器中检查请求头中的token,确保动态路由的安全性。引用3的配置里也有WebSocket转发的例子,但用户的问题主要关注基于规则的路由,可能需要更通用的解决方案。 总结下来,需要分步骤讲解GatewayZuul实现方式,包括配置文件和代码示例,以及动态更新的方法。同时,要提到如何利用配置中心或数据库存储路由规则,并通过API或事件机制动态更新路由。此外,对比两者的优缺点,帮助用户选择适合的方案。</think>### 通过Spring Cloud GatewayZuul实现基于规则的路由转发 #### 一、Spring Cloud Gateway实现动态路由 1. **基础配置** 在`application.yml`中定义路由规则,例如基于请求路径或请求头路由: ```yaml spring: cloud: gateway: routes: - id: service_route uri: lb://backend-service # 从注册中心拉取服务 predicates: - Path=/api/** # 匹配路径规则 - Header=X-Version, v2 # 匹配请求头规则 ``` 2. **动态路由实现** - **编程式配置**:通过`RouteLocatorBuilder`动态构建路由规则[^2]: ```java @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("dynamic_route", r -> r.path("/user/**") .and().header("X-Role", "admin") .uri("lb://admin-service")) .build(); } ``` - **集成配置中心**:结合Nacos或Consul,监听配置变化并刷新路由[^3]。 3. **过滤器链增强规则** 添加全局过滤器实现鉴权或流量控制: ```java @Bean public GlobalFilter customFilter() { return (exchange, chain) -> { if (!exchange.getRequest().getHeaders().containsKey("Token")) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); }; } ``` #### 二、Zuul实现动态路由 1. **基础配置** 在`application.yml`中定义静态路由: ```yaml zuul: routes: user-service: path: /user/** serviceId: user-service ``` 2. **动态路由扩展** - **自定义`RouteLocator`**:通过实现`RouteLocator`接口动态加载路由规则[^1]: ```java @Bean public RouteLocator dynamicRouteLocator() { return new RouteLocator() { @Override public List<Route> getRoutes() { List<Route> routes = new ArrayList<>(); routes.add(new Route("dynamic_route", "/order/**", "order-service", null)); return routes; } }; } ``` - **结合数据库**:从数据库读取路由配置,并通过定时任务刷新。 3. **过滤器实现复杂逻辑** 在`ZuulFilter`中修改请求路径或拦截非法请求: ```java public class AuthFilter extends ZuulFilter { @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); if (ctx.getRequest().getParameter("token") == null) { ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); } return null; } } ``` #### 三、对比与选型建议 1. **性能差异** - Gateway基于Netty异步非阻塞模型,适合高并发场景[^2]。 - Zuul 1.x为同步阻塞,适用于简单路由需求[^1]。 2. **动态能力** - Gateway原生支持动态路由,通过`RouteDefinitionRepository`接口扩展。 - Zuul需依赖自定义代码或第三方库(如Spring Cloud Config)。 3. **扩展性** Gateway提供更灵活的过滤器链(如限流、熔断),Zuul需依赖Hystrix等组件。 ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嗷嗷待哺丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值