1. Gateway和Zuul课程说明

Zuul开发人员窝里斗,实属明日黄花
重点关注Gate Way

2. Gateway简介

上一代zuul 1.x官网
Gateway官网

概述

Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;

但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,那就是SpringCloud Gateway—句话:gateway是原zuul1.x版的替代
六、服务网关 - 图1
Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和Project Reactor等技术。
Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。

SpringCloud Gateway是Spring Cloud的一个全新项目,基于Spring 5.0+Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供—种简单有效的统一的API路由管理方式。

SpringCloud Gateway作为Spring Cloud 生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty
image.png

Spring Cloud Gateway的目标提供统一的路由方式且基于 Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流

Geteway分类以及作用

image.png

微服务架构中网关的位置

六、服务网关 - 图4

3. 非阻塞异步模型

前言:有Zuull了怎么又出来Gateway?

我们为什么选择Gateway?

1. netflix不太靠谱,zuul2.0一直跳票,迟迟不发布

  • 一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是套件产品,值得信赖。而且很多功能Zuul都没有用起来也非常的简单便捷。
  • Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的Zuul 2.x,但Spring Cloud貌似没有整合计划。而且Netflix相关组件都宣布进入维护期;不知前景如何?
  • 多方面综合考虑Gateway是很理想的网关选择

2. SpringCloud Gateway具有如下特性

  • 基于Spring Framework 5,Project Reactor和Spring Boot 2.0进行构建;
  • 动态路由:能够匹配任何请求属性;
  • 可以对路由指定Predicate (断言)和Filter(过滤器);
  • 集成Hystrix的断路器功能;
  • 集成Spring Cloud 服务发现功能;
  • 易于编写的Predicate (断言)和Filter (过滤器);
  • 请求限流功能;
  • 支持路径重写。

3. SpringCloud Gateway与Zuul的区别

  • 在SpringCloud Finchley正式版之前,Spring Cloud推荐的网关是Netflix提供的Zuul。
  • Zuul 1.x 是一个基于阻塞I/O的API Gateway。
  • Zuul 1.x 基于Servlet 2.5使用阻塞架构它不支持任何长连接(如WebSocket)Zuul的设计模式和Nginx较像,每次I/О操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有第-次加载较慢的情况,使得Zuul的性能相对较差。
  • Zuul 2.x 理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul .x的性能较Zuul 1.x有较大提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍。
  • Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞API。
  • Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验

两者模型概念

1. Zuul1.x模型

Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Servlet IO处理模型。
Servlet的生命周期?servlet由servlet container进行生命周期管理

  • container启动时构造servlet对象并调用servlet init()进行初始化;
  • container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service);
  • container关闭时调用servlet destory()销毁servlet。

六、服务网关 - 图5
上述模式的缺点:
Servlet是一个简单的网络IO模型,当请求进入Servlet container时,Servlet container就会为其绑定一个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发(如抽风用Jmeter压),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势。

所以Zuul 1.X是基于servlet之上的一个阻塞式处理模型,即Spring实现了处理所有request请求的一个servlet (DispatcherServlet)并由该servlet阻塞式处理处理。所以SpringCloud Zuul无法摆脱servlet模型的弊端

2. Gateway模型

WebFlux是什么?官方文档

传统的Web框架,比如说: Struts2,SpringMVC等都是基于Servlet APl与Servlet容器基础之上运行的。
但是在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring 5必须让你使用Java 8)。

Spring WebFlux是Spring 5.0 引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet APl,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范

Spring Cloud Gateway requires the Netty runtime provided by Spring Boot and Spring Webflux. It does not work in a traditional Servlet Container or when built as a WAR

4. Gateway工作流程

三大核心概念

image.png
六、服务网关 - 图7
web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
predicate就是我们的匹配条件;而fliter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了

Gateway工作流程

The following diagram provides a high-level overview of how Spring Cloud Gateway works:
六、服务网关 - 图8

Clients make requests to Spring Cloud Gateway. If the Gateway Handler Mapping determines that a request matches a route, it is sent to the Gateway Web Handler. This handler runs the request through a filter chain that is specific to the request. The reason the filters are divided by the dotted line is that filters can run logic both before and after the proxy request is sent. All “pre” filter logic is executed. Then the proxy request is made. After the proxy request is made, the “post” filter logic is run.

客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到GatewayWeb Handler。 Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。 Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用

核心逻辑:路由转发+断言判断+过滤链执行

5. 网关服务案例

使用yaml配置网关路由

1. 新建Module - cloud-gateway-gateway9527

2. pom.xml

注意:不能添加web,actuator依赖包

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>SpringCloud</artifactId>
  7. <groupId>org.gmf</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>cloud-gateway-gateway9527</artifactId>
  12. <properties>
  13. <maven.compiler.source>8</maven.compiler.source>
  14. <maven.compiler.target>8</maven.compiler.target>
  15. </properties>
  16. <dependencies>
  17. <!--gateway-->
  18. <dependency>
  19. <groupId>org.springframework.cloud</groupId>
  20. <artifactId>spring-cloud-starter-gateway</artifactId>
  21. </dependency>
  22. <!--eureka-client-->
  23. <dependency>
  24. <groupId>org.springframework.cloud</groupId>
  25. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  26. </dependency>
  27. <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  28. <dependency>
  29. <groupId>org.gmf</groupId>
  30. <artifactId>cloud-api-commons</artifactId>
  31. <version>1.0-SNAPSHOT</version>
  32. </dependency>
  33. <!--一般基础配置类-->
  34. <dependency>
  35. <groupId>org.springframework.boot</groupId>
  36. <artifactId>spring-boot-devtools</artifactId>
  37. <scope>runtime</scope>
  38. <optional>true</optional>
  39. </dependency>
  40. <dependency>
  41. <groupId>org.projectlombok</groupId>
  42. <artifactId>lombok</artifactId>
  43. <optional>true</optional>
  44. </dependency>
  45. <dependency>
  46. <groupId>org.springframework.boot</groupId>
  47. <artifactId>spring-boot-starter-test</artifactId>
  48. <scope>test</scope>
  49. </dependency>
  50. </dependencies>
  51. </project>

image.png

3. 主启动类

@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527 {
    public static void main(String[] args) {
        SpringApplication.run(GatewayMain9527.class, args);
    }
}

4. application.yaml

server:
  port: 9527

spring:
  application:
    name: cloud-gateway

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

5. Gateway网关路由映射

cloud-provider-payment8001查看需要映射的Controller访问路径

@Slf4j
@RestController
@RequestMapping("/payment")
public class PaymentController {
    @Autowired
    private PaymentService paymentService;

    //引入import org.springframework.cloud.client.discovery.DiscoveryClient
    @Autowired
    private DiscoveryClient discoveryClient;

    //获取配置文件中的端口设置,用作展示区分
    @Value("${server.port}")
    private String port;

    @GetMapping("/getByid/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        if (payment!=null){
            return new CommonResult(200,"查询成功"+ port,payment);
        }else {
            return new CommonResult(444,"没有查询到对应记录",null);
        }
    }

    /**
     * @param
     * @return java.lang.String
     * @description 测试手写轮询功能
     */
    @GetMapping("/lb")
    public String getPaymentLB(){
        return port;
    }

    ...
}

我们目前不想暴露8001端口,希望在8001外面套一层9527,由访问9527来转发到8001
在application.yaml中添加对路由的映射

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  #添加Gate网关路由配置
  cloud:
    gateway:
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/getByid/**         # 断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由


eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

6. 测试

启动7001、cloud-provider-payment8001、9527Gateway
浏览器访问:http://eureka7001.com:7001/
已存在我们注册的两个服务
image.png

访问测试
访问网关前:http://localhost:8001/payment/getByid/1
访问网关后:http://localhost:9527/payment/getByid/1
结果:两个返回结果相同,淡化真实业务端口号,仅仅对外暴露网关端口号


使用Java配置类配置网关路由

配置类中注入RouteLocator的Bean,并在其中设置好路由映射
官方案例
image.png
业务需求:通过9527网关访问到其他外部网络http://news.baidu.com/guonei

Configuration

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GatewayConfig {
    /**
     * @param routeLocatorBuilder
     * @return org.springframework.cloud.gateway.route.RouteLocator
     * @description 自定义的路由映射
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
        //获取所有的路由映射
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        //设置路由映射,指定路由ID、断言匹配路径、服务提供路径
        routes.route("baidu",route -> route.path("/guonei").uri("http://news.baidu.com/guonei")).build();
        return routes.build();
    }
}

测试
浏览器输入http://localhost:9527/guonei,网关转发http://news.baidu.com/guonei相同的页面
image.png

6. GateWay配置动态路由

实际应用情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能,不再是写死一个地

pom.xml

<!--eureka-client-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

application.yaml

  • spring.cloud.gateway.discovery.locator.enabled = true开启从注册中心动态创建路由的功能,利用微服务名进行路由
  • spring.cloud.gateway.routes.uri = lb://serviceName匹配注册中心提供服务的名称 ```yaml server: port: 9527

spring: application: name: cloud-gateway

添加Gate网关路由配置

cloud: gateway: discovery: locator: enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由 routes:

    - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
      #uri: http://localhost:8001          #匹配后提供服务的路由地址
      uri: lb://cloud-payment-service   #匹配注册中心提供服务的名称
      predicates:
        - Path=/payment/getByid/**         # 断言,路径相匹配的进行路由

    - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
      #uri: http://localhost:8001          #匹配后提供服务的路由地址
      uri: lb://cloud-payment-service   #匹配注册中心提供服务的名称
      predicates:
        - Path=/payment/lb/**         # 断言,路径相匹配的进行路由

eureka: instance: hostname: cloud-gateway-service client: #服务提供者provider注册进eureka服务列表内 service-url: register-with-eureka: true fetch-registry: true defaultZone: http://eureka7001.com:7001/eureka


**测试**<br />启动Gateway9527,以及provider-payment8001/8002<br />浏览器访问:[http://localhost:9527/payment/lb](http://localhost:9527/payment/lb)<br />结果:不停刷新,8001/8002接口轮询更换


<a name="Xrl4R"></a>
## 7. GateWay常用的Predicate
<a name="98rLR"></a>
### 1. Predicate介绍
[官方文档](https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gateway-request-predicates-factories)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/12376550/1617289516943-c2b42f61-67bf-4782-8624-51c7c13ae580.png#height=92&id=Lc9ES&name=image.png&originHeight=92&originWidth=978&originalType=binary&ratio=1&size=21712&status=done&style=none&width=978)

> Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。
> Spring Cloud Gateway创建Route 对象时,使用RoutePredicateFactory 创建 Predicate对象,Predicate 对象可以赋值给Route。Spring Cloud Gateway包含许多内置的Route Predicate Factories。
> Spring Cloud Gateway创建Route 对象时,使用RoutePredicateFactory 创建 Predicate对象,Predicate 对象可以赋值给Route。Spring Cloud Gateway包含许多内置的Route Predicate Factories。
> 所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑andPredicate都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and


> **predicate**
> 美: ['predɪkeɪt] 英: ['predɪkət]
> v. 断言;使基于;使以…为依据;表明
> adj. 述语的;谓项的
> n. 谓语(句子成分,对主语加以陈述,如 John went home 中的 went home)


<a name="YG7CL"></a>
### 2. 常用的Route Predicate Factory

1. The After Route Predicate Factory
1. The Before Route Predicate Factory
1. The Between Route Predicate Factory
1. The Cookie Route Predicate Factory
1. The Header Route Predicate Factory
1. The Host Route Predicate Factory
1. The Method Route Predicate Factory
1. The Path Route Predicate Factory
1. The Query Route Predicate Factory
1. The RemoteAddr Route Predicate Factory
1. The weight Route Predicate Factory

在网关程序加载时也有展现<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/12376550/1617289700879-fb9e1099-a416-4147-ba72-729abbd191b1.png#height=354&id=eKgIu&name=image.png&originHeight=354&originWidth=1113&originalType=binary&ratio=1&size=105445&status=done&style=shadow&width=1113)


<a name="R287e"></a>
### 3. 演示Route Predicate Factory
<a name="ICpoe"></a>
#### 1. The After Route Predicate Factory
```yaml
spring:
  cloud:
    gateway:
      routes:
        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service   #匹配注册中心提供服务的名称
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由
            # 这个时间后才能起效
            - After=2017-01-20T17:42:47.789-07:00[America/Denver] # 中国时区2021-04-01T23:17:05.874+08:00[Asia/Shanghai]

但我们需要指定中国时间,介绍一个API获取中国时间

可以通过下述方法获得上述格式的时间戳字符串

public class TestTime {
    public static void main(String[] args) {
        ZonedDateTime now = ZonedDateTime.now();
        System.out.println(now);  //2021-04-01T23:17:05.874+08:00[Asia/Shanghai]
    }
}

测试
修改After断言超过当前时间
image.png
浏览器访问报错
image.png
修改时间早与当前时间
image.png
浏览器访问成功

2. The Between Route Predicate Factory

spring:
  cloud:
    gateway:
      routes:
        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service   #匹配注册中心提供服务的名称
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由
                - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

3. The Cookie Route Predicate Factory

spring:
  cloud:
    gateway:
      routes:
        - id: payment_routh2 #payment_route
          #uri: http://localhost:8001
          uri: lb://cloud-payment-service
          predicates:
                - Cookie=chocolate, ch.p  #该路由匹配具有一个名为chocolate的cookie的请求,该cookie的值与ch.p正则表达式匹配。

The cookie route predicate factory takes two parameters, the cookie name and a regular expression. This predicate matches cookies that have the given name and whose values match the regular expression. The following example configures a cookie route predicate factory

使用CMD CURL进行请求测试

# 该命令相当于发get请求,且没带cookie
curl http://localhost:9527/payment/lb

# 带cookie的
curl http://localhost:9527/payment/lb --cookie "chocolate=chip"

CMD命令访问测试
image.png

4. The Header Route Predicate Factory

spring:
  cloud:
    gateway:
      routes:
        - id: payment_routh2 #payment_route
          #uri: http://localhost:8001
          uri: lb://cloud-payment-service
          predicates:
                - Header=X-Request-Id, \d+

The header route predicate factory takes two parameters, the header name and a regular expression. header路由断言带有两个参数,参数名+正则表达式 This predicate matches with a header that has the given name whose value matches the regular expression

使用CMD CURL进行请求测试

# 带指定请求头的参数的CURL命令
curl http://localhost:9527/payment/lb -H "X-Request-Id:123"

5. The Method Route Predicate Factory

spring:
  cloud:
    gateway:
      routes:
        - id: payment_routh2 #payment_route
          #uri: http://localhost:8001
          uri: lb://cloud-payment-service
          predicates:
                - Method=GET,POST

如果请求方法是GET或POST,则此路由匹配

6. The Query Route Predicate Factory

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://example.org
        predicates:
        - Query=green

如果请求中包含green查询参数,则上述路由匹配。

其它的,举一反三。

5. 小结

说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理

8. GateWay的Filter

1. Gateway Filter简介

官方文档

Route filters allow the modification of the incoming HTTP request or outgoing HTTP response in some manner. Route filters are scoped to a particular route. Spring Cloud Gateway includes many built-in GatewayFilter Factories.

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生

2. Gateway Filter生命周期

  • pre —— 在业务逻辑之前
  • post —— 在业务逻辑之后


3. 种类

具体查看官方文档

GatewayFilter - 31种

image.pngimage.pngimage.png
常用的GatewayFilter:AddRequestParameter GatewayFilter

GlobalFilter - 10种

image.png

4. 自定义全局GlobalFilter

能干什么:

  1. 全局日志记录
  2. 统一网关鉴权

代码案例

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Date;

/**
 * @className: MyGlobalGatewayFilter
 * @description 自定义的Gateway全局过滤器
 * @author GMF
 * @date 2021/4/2
 * @time 0:22
*/
@Component
@Slf4j
@Order(Integer.MIN_VALUE)
public class MyGlobalGatewayFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("***********come in MyLogGateWayFilter:  "+new Date());
        //获取请求参数
        String username = exchange.getRequest().getQueryParams().getFirst("username");
        //判断请求参数是否符合条件
        if (StringUtils.isEmpty(username)){
            log.info("*******用户名为null,非法用户,o(╥﹏╥)o");
            //设置响应状态
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        //过滤器放行,交由下一个过滤器处理
        return chain.filter(exchange);
    }
}

测试

浏览器访问:http://localhost:9527/payment/lb?username=gmf
结果成功返回数据

浏览器访问:http://localhost:9527/payment/lb
结果响应错误页面
image.png