云原生之 Gateway 的 Filter 过滤器

为什么会出现

通常情况下,出于安全方面的考虑,服务端提供的服务往往都会有一定的校验逻辑,例如用户登陆状态校验、签名校验等。

在微服务架构中,系统由多个微服务组成,所有这些服务都需要这些校验逻辑,此时我们就可以将这些校验逻辑写到 Spring Cloud Gateway 的 Filter 过滤器中。

Filter 的分类

Spring Cloud Gateway 提供了以下两种类型的过滤器,可以对请求和响应进行精细化控制。

过滤器类型

说明

Pre 类型

这种过滤器在请求被转发到微服务之前可以对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等操作。

Post 类型

这种过滤器在微服务对请求做出响应后可以对响应进行拦截和再处理,例如修改响应内容或响应头、日志输出、流量监控等。

按照作用范围划分,Spring Cloud gateway 的 Filter 可以分为 2 类:

  • GatewayFilter:应用在单个路由或者一组路由上的过滤器。
  • GlobalFilter:应用在所有的路由上的过滤器。

GatewayFilter 网关过滤器 

GatewayFilter 是 Spring Cloud Gateway 网关中提供的一种应用在单个或一组路由上的过滤器。它可以对单个路由或者一组路由上传入的请求和传出响应进行拦截,并实现一些与业务无关的功能,比如登陆状态校验、签名校验、权限校验、日志输出、流量监控等。

GatewayFilter 在配置文件(例如 application.yml)中的写法与 Predicate 类似,格式如下。

代码语言:javascript
复制
spring:
cloud:
gateway:
routes:
- id: xxxx
uri: xxxx
predicates:
- Path=xxxx
filters:
- AddRequestParameter=X-Request-Id,1024 #过滤器工厂会在匹配的请求头加上一对请求头,名称为 X-Request-Id 值为 1024
- PrefixPath=/dept #在请求路径前面加上 /dept
……

Spring Cloud Gateway 内置了多达 31 种 GatewayFilter,下表中列举了几种常用的网关过滤器及其使用示例。

路由过滤器

描述

参数

使用示例

AddRequestHeader

拦截传入的请求,并在请求上添加一个指定的请求头参数。

name:需要添加的请求头参数的 key; value:需要添加的请求头参数的 value。

- AddRequestHeader=my-request-header,1024

AddRequestParameter

拦截传入的请求,并在请求上添加一个指定的请求参数。

name:需要添加的请求参数的 key; value:需要添加的请求参数的 value。

- AddRequestParameter=my-request-param,c.biancheng.net

AddResponseHeader

拦截响应,并在响应上添加一个指定的响应头参数。

name:需要添加的响应头的 key; value:需要添加的响应头的 value。

- AddResponseHeader=my-response-header,c.biancheng.net

PrefixPath

拦截传入的请求,并在请求路径增加一个指定的前缀。

prefix:需要增加的路径前缀。

- PrefixPath=/consumer

PreserveHostHeader

转发请求时,保持客户端的 Host 信息不变,然后将它传递到提供具体服务的微服务中。

- PreserveHostHeader

RemoveRequestHeader

移除请求头中指定的参数。

name:需要移除的请求头的 key。

- RemoveRequestHeader=my-request-header

RemoveResponseHeader

移除响应头中指定的参数。

name:需要移除的响应头。

- RemoveResponseHeader=my-response-header

RemoveRequestParameter

移除指定的请求参数。

name:需要移除的请求参数。

- RemoveRequestParameter=my-request-param

RequestSize

配置请求体的大小,当请求体过大时,将会返回 413 Payload Too Large。

maxSize:请求体的大小。

- name: RequestSize    args:      maxSize: 5000000

 网关过滤器示例

下面我们通过一个实例来演示 GatewayFilter 的配置,步骤如下。

1. 在 micro-service-cloud-gateway-9527 的 application.yml 中在添加一个动态路由,配置内容如下

代码语言:javascript
复制
- id: provider_dept_get_routh
uri: lb://MICROSERVICECLOUDPROVIDERDEPT #使用服务名代替上面的具体带端口
predicates:
- Path=/get/**
filters:
- PrefixPath=/dept #在请求路径上增加一个前缀 /dept

 2. 重启 micro-service-cloud-gateway-9527,使用浏览器访“http://eureka7001.com/get/1”,结果成功转发


GlobalFilter 全局过滤器

GlobalFilter 是一种作用于所有的路由上的全局过滤器,通过它,我们可以实现一些统一化的业务功能,例如权限认证、IP 访问限制等。当某个请求被路由匹配时,那么所有的 GlobalFilter 会和该路由自身配置的 GatewayFilter 组合成一个过滤器链。

Spring Cloud Gateway 为我们提供了多种默认的 GlobalFilter,例如与转发、路由、负载均衡等相关的全局过滤器。但在实际的项目开发中,通常我们都会自定义一些自己的 GlobalFilter 全局过滤器以满足我们自身的业务需求,而很少直接使用 Spring Cloud  Config 提供这些默认的 GlobalFilter。

全局过滤器实例

1. 在 net.biancheng.c.filter 包下,新建一个名为 MyGlobalFilter 全局过滤器配置类,代码如下。

代码语言:javascript
复制
package net.biancheng.c.filter;

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.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Date;

/**

  • 自定义全局网关过滤器(GlobalFilter)
    */
    @Component
    @Slf4j
    public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    log.info("进入自定义的全局过滤器 MyGlobalFilter" + new Date());
    String uname = exchange.getRequest().getQueryParams().getFirst("uname");
    if (uname == null) {
    log.info("参数 uname 不能为 null!");
    exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
    return exchange.getResponse().setComplete();
    }
    return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
    //过滤器的顺序,0 表示第一个
    return 0;
    }
    }

 2. 重启 micro-service-cloud-gateway-9527,使用浏览器访问“http://eureka7001.com/dept/list”,我们会发现访问报 406 错误,控制台输出如下。

代码语言:javascript
复制
2022-11-1 16:25:39.450  INFO 19116 --- [ctor-http-nio-4] net.biancheng.c.filter.MyGlobalFilter    : CST 2021进入自定义的全局过滤器 MyGlobalFilter
2022-11-1 16:25:39.451 INFO 19116 --- [ctor-http-nio-4] net.biancheng.c.filter.MyGlobalFilter : 参数 uname 不能为 null!

 3. 使用浏览器访问“http://eureka7001.com/dept/list?uname=123”,则会成功转发

Spring Cloud Gateway 工作流程 

  1. 客户端将请求发送到 Spring Cloud Gateway 上。
  2. Spring Cloud Gateway 通过 Gateway Handler Mapping 找到与请求相匹配的路由,将其发送给 Gateway Web Handler。
  3. Gateway Web Handler 通过指定的过滤器链(Filter Chain),将请求转发到实际的服务节点中,执行业务逻辑返回响应结果。
  4. 过滤器之间用虚线分开是因为过滤器可能会在转发请求之前(pre)或之后(post)执行业务逻辑。
  5. 过滤器(Filter)可以在请求被转发到服务端前,对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等。
  6. 过滤器可以在响应返回客户端之前,对响应进行拦截和再处理,例如修改响应内容或响应头、日志输出、流量监控等。
  7. 响应原路返回给客户端。


总而言之,客户端发送到 Spring Cloud Gateway 的请求需要通过一定的匹配条件,才能定位到真正的服务节点。在将请求转发到服务进行处理的过程前后(pre 和 post),我们还可以对请求和响应进行一些精细化控制。

Predicate 就是路由的匹配条件,而 Filter 就是对请求和响应进行精细化控制的工具。有了这两个元素,再加上目标 URI,就可以实现一个具体的路由了。