系统整理springCloud系列(十)网关

一。zuul简介

提供=代理+路由+过滤三大功能
Zuul包含了对请求的路由和过滤两个最主要的功能:
其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础

而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础.

Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。
 
    注意:Zuul服务最终还是会注册进Eureka
 
提供=代理+路由+过滤三大功能

官网:https://github.com/Netflix/zuul/wiki/Getting-Started

路由的基本配置:

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

主启动

@EnableZuulProxy

路由规则

zuul: 
  routes: 
    mydept.serviceId: microservicecloud-dept
    mydept.path: /mydept/**

访问路径

http://127.0.0.1:10010/microservicecloud-dept/dept/get/2

原真实服务名可忽略

zuul: 
  ignored-services: "*"
  routes: 
    mydept.serviceId: microservicecloud-dept
    mydept.path: /mydept/**

 

设置统一前缀

zuul: 
  prefix: /liu
  ignored-services: "*"
  routes: 
    mydept.serviceId: microservicecloud-dept
    mydept.path: /mydept/**

http://127.0.0.1:10010/liu/mydept/dept/get/1

server: 
  port: 9527
 
spring: 
  application:
    name: microservicecloud-zuul-gateway
 
zuul: 
  prefix: /liu
  ignored-services: "*"
  routes: 
    mydept.serviceId: microservicecloud-dept
    mydept.path: /mydept/**
 
eureka: 
  client: 
    service-url: 
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka  
  instance:
    instance-id: gateway-9527.com
    prefer-ip-address: true 
 
info:
  app.name: liu-microcloud
  company.name: www.baidu.com
  build.artifactId: $project.artifactId$
  build.version: $project.version$
 

zuul只简单介绍一下

现在详细介绍常用网关

gateway

Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。
Spring Cloud Gateway 可以看做是一个 Zuul 1.x 的升级版和代替品,比 Zuul 2 更早的使用 Netty 实现异步 IO,从而实现了一个简单、比 Zuul 1.x 更高效的、与 Spring Cloud 紧密配合的 API 网关。
Spring Cloud Gateway 里明确的区分了 Router 和 Filter,并且一个很大的特点是内置了非常多的开箱即用功能,并且都可以通过 SpringBoot 配置或者手工编码链式调用来使用。
比如内置了 10 种 Router,使得我们可以直接配置一下就可以随心所欲的根据 Header、或者 Path、或者 Host、或者 Query 来做路由。
比如区分了一般的 Filter 和全局 Filter,内置了 20 种 Filter 和 9 种全局 Filter,也都可以直接用。当然自定义 Filter 也非常方便。

系统整理springCloud系列(十)网关

项目中网关的位置

系统整理springCloud系列(十)网关 

几个概念: 

系统整理springCloud系列(十)网关

 

系统整理springCloud系列(十)网关 

 

与zuul相比,为什么选择gateway呢

系统整理springCloud系列(十)网关

系统整理springCloud系列(十)网关

zuul1.X模型 

系统整理springCloud系列(十)网关

gateway模型 

系统整理springCloud系列(十)网关 

gateway工作流程

系统整理springCloud系列(十)网关 

核心逻辑:路由转发+执行过滤器链

gateway概念,下面建项目介绍具体如何应用。 

pom:

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

yml

server:
  port: 9527
spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true  #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001   #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路由

        - id: payment_routh2
          #uri: http://localhost:8001   #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/payment/lb/**   #断言,路径相匹配的进行路由

        - id: route_guonei
          uri: http://www.baidu.com
          predicates:
            - Path=guonei   #断言,路径相匹配的进行路由,失败


eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

 

主启动

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

 

 

服务提供方服务接口

@GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id) {
        Payment payment = paymentService.getPaymentById(id);
        log.info("查询结果:"+payment);
        if(payment != null) {
            return new CommonResult(200, "查询成功,端口号:"+serverPort, payment);
        } else {
            return new CommonResult(444, "没有对应记录,查询ID:"+id, null);
        }
    }
    @GetMapping(value = "/payment/lb")
    public String getPaymentLB(){
        return serverPort;
    }

8001服务访问

 

系统整理springCloud系列(十)网关

系统整理springCloud系列(十)网关 

系统整理springCloud系列(十)网关 

启动网关服务

系统整理springCloud系列(十)网关

系统整理springCloud系列(十)网关

系统整理springCloud系列(十)网关 

 系统整理springCloud系列(十)网关

常用的Route Predicate

Route Predicate Factories
Spring Cloud Gateway是使用Spring WebFlux的HandlerMapping作为匹配路由底层实现,本身已自带很多Route Predicate Factories,分别匹配不同的http请求属性,多个Route Predicate Factories也可以通过and进行逻辑合并匹配。

1. After Route Predicate Factory
After Route Predicate Factory使用的是时间作为匹配规则,只要当前时间大于设定时间,路由才会匹配请求。
application.yml:

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: http://www.google.com
        predicates:
        - After=2020-07-25T14:33:47.789+08:00

这个路由规则会在东8区的2020-07-2514:33:47后,将请求都转跳到google。

2. Before Route Predicate Factory
Before Route Predicate Factory也是使用时间作为匹配规则,只要当前时间小于设定时间,路由才会匹配请求。
application.yml:

spring:
  cloud:
    gateway:
      routes:
      - id: before_route
        uri: http://www.google.com
        predicates:
        - Before=2020-07-25T14:33:47.789+08:00

这个路由规则会在东8区的2020-07-25 14:33:47前,将请求都转跳到google。

3. Between Route Predicate Factory
Between Route Predicate Factory也是使用两个时间作为匹配规则,只要当前时间大于第一个设定时间,并小于第二个设定时间,路由才会匹配请求。

application.yml:

spring:
  cloud:
    gateway:
      routes:
      - id: between_route
        uri: http://www.google.com
        predicates:
        - Between=2020-07-25T14:33:47.789+08:00,2020-07-26T14:33:47.789+08:00

这个路由规则会在东8区的2020-07-25 14:33:47到2020-07-26 14:33:47之间,将请求都转跳到google。

4. Cookie Route Predicate Factory
Cookie Route Predicate Factory使用的是cookie名字和正则表达式的value作为两个输入参数,请求的cookie需要匹配cookie名和符合其中value的正则。
application.yml:

spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: http://www.google.com
        predicates:
        - Cookie=cookiename, cookievalue

路由匹配请求存在cookie名为cookiename,cookie内容匹配cookievalue的,将请求转发到google。

5. Header Route Predicate Factory
Header Route Predicate Factory,与Cookie Route Predicate Factory类似,也是两个参数,一个header的name,一个是正则匹配的value。
application.yml:

spring:
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: http://www.google.com
        predicates:
        - Header=X-Request-Id, \d+


路由匹配存在名为X-Request-Id,内容为数字的header的请求,将请求转发到google。

6. Host Route Predicate Factory
Host Route Predicate Factory使用的是host的列表作为参数,host使用Ant style匹配。
application.yml:

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: http://www.google.com
        predicates:
        - Host=**.somehost.org,**.anotherhost.org

路由会匹配Host诸如:www.somehost.org 或 beta.somehost.org或www.anotherhost.org等请求。

7. Method Route Predicate Factory
Method Route Predicate Factory是通过HTTP的method来匹配路由。
application.yml:

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: http://www.google.com
        predicates:
        - Method=GET

路由会匹配到所有GET方法的请求。

8. Path Route Predicate Factory
Path Route Predicate Factory使用的是path列表作为参数,使用Spring的PathMatcher匹配path,可以设置可选变量。
application.yml:

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: http://www.google.com
        predicates:
        - Path=/foo/{segment},/bar/{segment}

上面路由可以匹配诸如:/foo/1 或 /foo/bar 或 /bar/baz等
其中的segment变量可以通过下面方式获取:

PathMatchInfo variables = exchange.getAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE);
Map<String, String> uriVariables = variables.getUriVariables();
String segment = uriVariables.get("segment");

在后续的GatewayFilter Factories就可以做对应的操作了。

9. Query Route Predicate Factory
Query Route Predicate Factory可以通过一个或两个参数来匹配路由,一个是查询的name,一个是查询的正则value。
application.yml:

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: http://www.google.com
        predicates:
        - Query=baz

路由会匹配所有包含baz查询参数的请求。
application.yml:

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: http://www.google.com
        predicates:
        - Query=foo, ba.

路由会匹配所有包含baz,并且baz的内容为诸如:bar或baz等符合ba.正则规则的请求。

10. RemoteAddr Route Predicate Factory
RemoteAddr Route Predicate Factory通过无类别域间路由(IPv4 or IPv6)列表匹配路由。
application.yml:

spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: http://www.google.com
        predicates:
        - RemoteAddr=192.168.1.1/24

上面路由就会匹配RemoteAddr诸如192.168.1.10等请求。

10.1 Modifying the way remote addresses are resolved
RemoteAddr Route Predicate Factory默认情况下,使用的是请求的remote address。但是如果Spring Cloud Gateway是部署在其他的代理后面的,如Nginx,则Spring Cloud Gateway获取请求的remote address是其他代理的ip,而不是真实客户端的ip。

考虑到这种情况,你可以自定义获取remote address的处理器RemoteAddressResolver。当然Spring Cloud Gateway也提供了基于X-Forwarded-For请求头的XForwardedRemoteAddressResolver。
熟悉Http代理协议的,都知道X-Forwarded-For头信息做什么的,不熟悉的可以自己谷歌了解一下。

XForwardedRemoteAddressResolver提供了两个静态方法获取它的实例:
XForwardedRemoteAddressResolver::trustAll得到的RemoteAddressResolver总是获取X-Forwarded-For的第一个ip地址作为remote address,这种方式就比较容易被伪装的请求欺骗,模拟请求很容易通过设置初始的X-Forwarded-For头信息,就可以欺骗到gateway。

XForwardedRemoteAddressResolver::maxTrustedIndex得到的RemoteAddressResolver则会在X-Forwarded-For信息里面,从右到左选择信任最多maxTrustedIndex个ip,因为X-Forwarded-For是越往右是越接近gateway的代理机器ip,所以是越往右的ip,信任度是越高的。
那么如果前面只是挡了一层Nginx的话,如果只需要Nginx前面客户端的ip,则maxTrustedIndex取1,就可以比较安全地获取真实客户端ip。

gateway  filter使用

系统整理springCloud系列(十)网关

系统整理springCloud系列(十)网关 

现在挑几个常见的过滤器工厂来讲解,每一个过滤器工厂在官方文档都给出了详细的使用案例,如果不清楚的还可以在org.springframework.cloud.gateway.filter.factory看每一个过滤器工厂的源码。

AddRequestHeader GatewayFilter Factory
创建工程,引入相关的依赖,包括spring boot 版本2.0.5,spring Cloud版本Finchley,gateway依赖如下:

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


在工程的配置文件中,加入以下的配置:

server:
  port: 8081
spring:
  profiles:
    active: add_request_header_route

---
spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: http://httpbin.org:80/get
        filters:
        - AddRequestHeader=X-Request-Foo, Bar
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]
  profiles: add_request_header_route


在上述的配置中,工程的启动端口为8081,配置文件为add_request_header_route,在add_request_header_route配置中,配置了roter的id为add_request_header_route,路由地址为http://httpbin.org:80/get,该router有AfterPredictFactory,有一个filter为AddRequestHeaderGatewayFilterFactory(约定写成AddRequestHeader),AddRequestHeader过滤器工厂会在请求头加上一对请求头,名称为X-Request-Foo,值为Bar。为了验证AddRequestHeaderGatewayFilterFactory是怎么样工作的,查看它的源码,AddRequestHeaderGatewayFilterFactory的源码如下:


public class AddRequestHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {

    @Override
    public GatewayFilter apply(NameValueConfig config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest().mutate()
                    .header(config.getName(), config.getValue())
                    .build();

            return chain.filter(exchange.mutate().request(request).build());
        };
    }

}


由上面的代码可知,根据旧的ServerHttpRequest创建新的 ServerHttpRequest ,在新的ServerHttpRequest加了一个请求头,然后创建新的 ServerWebExchange ,提交过滤器链继续过滤。

启动工程,通过curl命令来模拟请求:


curl localhost:8081


最终显示了从 http://httpbin.org:80/get得到了请求,响应如下:

{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Connection": "close",
    "Forwarded": "proto=http;host=\"localhost:8081\";for=\"0:0:0:0:0:0:0:1:56248\"",
    "Host": "httpbin.org",
    "User-Agent": "curl/7.58.0",
    "X-Forwarded-Host": "localhost:8081",
    "X-Request-Foo": "Bar"
  },
  "origin": "0:0:0:0:0:0:0:1, 210.22.21.66",
  "url": "http://localhost:8081/get"
}


可以上面的响应可知,确实在请求头中加入了X-Request-Foo这样的一个请求头,在配置文件中配置的AddRequestHeader过滤器工厂生效。

跟AddRequestHeader过滤器工厂类似的还有AddResponseHeader过滤器工厂,在此就不再重复。

RewritePath GatewayFilter Factory
在Nginx服务启中有一个非常强大的功能就是重写路径,Spring Cloud Gateway默认也提供了这样的功能,这个功能是Zuul没有的。在配置文件中加上以下的配置:


spring:
  profiles:
    active: rewritepath_route
---
spring:
  cloud:
    gateway:
      routes:
      - id: rewritepath_route
        uri: https://blog.****.net
        predicates:
        - Path=/foo/**
        filters:
        - RewritePath=/foo/(?<segment>.*), /$\{segment}
  profiles: rewritepath_route


上面的配置中,所有的/foo/**开始的路径都会命中配置的router,并执行过滤器的逻辑,在本案例中配置了RewritePath过滤器工厂,此工厂将/foo/(?.*)重写为{segment},然后转发到https://blog.****.net。比如在网页上请求localhost:8081/foo/forezp,此时会将请求转发到https://blog.****.net/forezp的页面,比如在网页上请求localhost:8081/foo/forezp/1,页面显示404,就是因为不存在https://blog.****.net/forezp/1这个页面。

自定义过滤器
Spring Cloud Gateway内置了19种强大的过滤器工厂,能够满足很多场景的需求,那么能不能自定义自己的过滤器呢,当然是可以的。在spring Cloud Gateway中,过滤器需要实现GatewayFilter和Ordered2个接口。写一个RequestTimeFilter,代码如下:


public class RequestTimeFilter implements GatewayFilter, Ordered {

    private static final Log log = LogFactory.getLog(GatewayFilter.class);
    private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
                    if (startTime != null) {
                        log.info(exchange.getRequest().getURI().getRawPath() + ": " + (System.currentTimeMillis() - startTime) + "ms");
                    }
                })
        );

    }

    @Override
    public int getOrder() {
        return 0;
    }
}

在上面的代码中,Ordered中的int getOrder()方法是来给过滤器设定优先级别的,值越大则优先级越低。还有有一个filterI(exchange,chain)方法,在该方法中,先记录了请求的开始时间,并保存在ServerWebExchange中,此处是一个“pre”类型的过滤器,然后再chain.filter的内部类中的run()方法中相当于"post"过滤器,在此处打印了请求所消耗的时间。然后将该过滤器注册到router中,代码如下:

    @Bean
    public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
        // @formatter:off
        return builder.routes()
                .route(r -> r.path("/customer/**")
                        .filters(f -> f.filter(new RequestTimeFilter())
                                .addResponseHeader("X-Response-Default-Foo", "Default-Bar"))
                        .uri("http://httpbin.org:80/get")
                        .order(0)
                        .id("customer_filter_router")
                )
                .build();
        // @formatter:on
    }



重启程序,通过curl命令模拟请求:

 curl localhost:8081/customer/123


在程序的控制台输出一下的请求信息的日志:


2018-11-16 15:02:20.177  INFO 20488 --- [ctor-http-nio-3] o.s.cloud.gateway.filter.GatewayFilter   : /customer/123: 152ms


自定义过滤器工厂
在上面的自定义过滤器中,有没有办法自定义过滤器工厂类呢?这样就可以在配置文件中配置过滤器了。现在需要实现一个过滤器工厂,在打印时间的时候,可以设置参数来决定是否打印请参数。查看GatewayFilterFactory的源码,可以发现GatewayFilterfactory的层级如下:

过滤器工厂的顶级接口是GatewayFilterFactory,我们可以直接继承它的两个抽象类来简化开发AbstractGatewayFilterFactory和AbstractNameValueGatewayFilterFactory,这两个抽象类的区别就是前者接收一个参数(像StripPrefix和我们创建的这种),后者接收两个参数(像AddResponseHeader)。

过滤器工厂的顶级接口是GatewayFilterFactory,有2个两个较接近具体实现的抽象类,分别为AbstractGatewayFilterFactory和AbstractNameValueGatewayFilterFactory,这2个类前者接收一个参数,比如它的实现类RedirectToGatewayFilterFactory;后者接收2个参数,比如它的实现类AddRequestHeaderGatewayFilterFactory类。现在需要将请求的日志打印出来,需要使用一个参数,这时可以参照RedirectToGatewayFilterFactory的写法。


public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> {


    private static final Log log = LogFactory.getLog(GatewayFilter.class);
    private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";
    private static final String KEY = "withParams";

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(KEY);
    }

    public RequestTimeGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
            return chain.filter(exchange).then(
                    Mono.fromRunnable(() -> {
                        Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
                        if (startTime != null) {
                            StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
                                    .append(": ")
                                    .append(System.currentTimeMillis() - startTime)
                                    .append("ms");
                            if (config.isWithParams()) {
                                sb.append(" params:").append(exchange.getRequest().getQueryParams());
                            }
                            log.info(sb.toString());
                        }
                    })
            );
        };
    }


    public static class Config {

        private boolean withParams;

        public boolean isWithParams() {
            return withParams;
        }

        public void setWithParams(boolean withParams) {
            this.withParams = withParams;
        }

    }
}



在上面的代码中 apply(Config config)方法内创建了一个GatewayFilter的匿名类,具体的实现逻辑跟之前一样,只不过加了是否打印请求参数的逻辑,而这个逻辑的开关是config.isWithParams()。静态内部类类Config就是为了接收那个boolean类型的参数服务的,里边的变量名可以随意写,但是要重写List shortcutFieldOrder()这个方法。

需要注意的是,在类的构造器中一定要调用下父类的构造器把Config类型传过去,否则会报ClassCastException

最后,需要在工程的启动文件Application类中,向Srping Ioc容器注册RequestTimeGatewayFilterFactory类的Bean。


    @Bean
    public RequestTimeGatewayFilterFactory elapsedGatewayFilterFactory() {
        return new RequestTimeGatewayFilterFactory();
    }


然后可以在配置文件中配置如下:

spring:
  profiles:
    active: elapse_route

---
spring:
  cloud:
    gateway:
      routes:
      - id: elapse_route
        uri: http://httpbin.org:80/get
        filters:
        - RequestTime=false
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]
  profiles: elapse_route


启动工程,在浏览器上访问localhost:8081?name=forezp,可以在控制台上看到,日志输出了请求消耗的时间和请求参数。

global filter
Spring Cloud Gateway根据作用范围划分为GatewayFilter和GlobalFilter,二者区别如下:

GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上

GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。

Spring Cloud Gateway框架内置的GlobalFilter如下:

上图中每一个GlobalFilter都作用在每一个router上,能够满足大多数的需求。但是如果遇到业务上的定制,可能需要编写满足自己需求的GlobalFilter。在下面的案例中将讲述如何编写自己GlobalFilter,该GlobalFilter会校验请求中是否包含了请求参数“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。代码如下:


public class TokenFilter implements GlobalFilter, Ordered {

    Logger logger=LoggerFactory.getLogger( TokenFilter.class );
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (token == null || token.isEmpty()) {
            logger.info( "token is empty..." );
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -100;
    }
}


在上面的TokenFilter需要实现GlobalFilter和Ordered接口,这和实现GatewayFilter很类似。然后根据ServerWebExchange获取ServerHttpRequest,然后根据ServerHttpRequest中是否含有参数token,如果没有则完成请求,终止转发,否则执行正常的逻辑。

然后需要将TokenFilter在工程的启动类中注入到Spring Ioc容器中,代码如下:


@Bean
public TokenFilter tokenFilter(){
        return new TokenFilter();
}


启动工程,使用curl命令请求:

 curl localhost:8081/customer/123
 

可以看到请没有被转发,请求被终止,并在控制台打印了如下日志:

2018-11-16 15:30:13.543  INFO 19372 --- [ctor-http-nio-2] gateway.TokenFilter                      : token is empty...

上面的日志显示了请求进入了没有传“token”的逻辑。

自定义过滤器

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("*********come in MyLogGateWayFilter: "+new Date());
        String uname = exchange.getRequest().getQueryParams().getFirst("username");
        if(StringUtils.isEmpty(uname)){
            log.info("*****用户名为Null 非法用户,(┬_┬)");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//给人家一个回应
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);

    }

    @Override
    public int getOrder() {
        return 0;
    }
}