谓词 After · SpringCloud微服务实战 · 看云

1. 谓词 After

导航

下面我们开始第一个谓词After ,该谓词后面需要拼接一个参数,即日期时间。该谓词匹配在指定日期时间之后发生的请求。匹配不到会返回404

本节代码地址


1.1 新建项目

为了演示方便,我们新建一个项目,并且后面爱他问题也使用这个项目,我们使用多profile 构建项目,每个profile 是一张类型。
6aa8c5581d6b8bdb615b6e8be8fb1f1b_MD5.webp

1.2 maven 配置

Spring Cloud Gateway 是使用 netty+webflux 实现因此不需要再引入 spring-boot-starter-web包,这里我们引入Eureka,是为了直接通过Eureka 获取注册服务并发送请求。

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

1.3 新建启动类

可以看出,启动类的配置和引入spring-boot-starter-web包的项目没什么区别。


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

1.4 应用配置

为了后期阅读方便,我们使用多profile的形式来配置应用,对于多profile不了解的话,后面笔者会在springboot 章节加上这一块的讲解。这里我们配置了端口和服务名以及当前使用的profile和注册中心。对于Spring Cloud 网关我们配置了在2020年1月8号18:30:11.965之后才可以访问的路由,请求路由的时候回拿当前时间和配置的时间做对比。成功的话我们会路由到fw-cloud-ribbon-server服务上去。
使用以下代码可以打印带有时区的当前时间,然后再自行修改成特定时间即可:

System.out.println(ZonedDateTime.now());

server:
  port: 8699
spring:
  application:
    name: fw-gateways-gateway
  profiles:
    active: after_route
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
---
spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: lb://fw-cloud-ribbon-server
          predicates: 
          - After=2020-01-08T18:30:11.965+08:00[Asia/Shanghai]
  profiles: after_route

Gateway配置含义如下:

  • id:我们自定义的路由 ID,保持唯一
  • uri:目标服务地址
  • predicates:路由条件,支持多个参数。
  • filters:过滤规则,支持多个参数,可以和predicates共用。

1.5 启动项目

a8c0db3933ece71f82531bbd4687761d_MD5.png

浏览器或Postman 输入localhost:8699/user/1
ce640032c2dc600bff1da832a636a7f4_MD5.png

将时间调大

- After=2021-01-08T18:30:11.965+08:00[Asia/Shanghai]

再次测试可以看到404,因为没有转发
3a520129e1dd93da6eeb64495810c89b_MD5.png

1.6 AfterRoutePredicateFactory 源码

可以看到仅仅是获取当前时间,然后和配置的时间作比较,之后交给NettyRoutingFilter执行

@Override
public Predicate<ServerWebExchange> apply(Config config) {
   return new GatewayPredicate() {
      @Override
      public boolean test(ServerWebExchange serverWebExchange) {
         final ZonedDateTime now = ZonedDateTime.now();
         return now.isAfter(config.getDatetime());
      }

      @Override
      public String toString() {
         return String.format("After: %s", config.getDatetime());
      }
   };
}

1.7 NettyRoutingFilter 源码

所有的谓词、过滤器等配置信息初始化完成之后,最终都会通过这个方法发请求。
如果已经是路由过的,直接执行
如果没有路由过,先设置为已路由
获取header 、url 等请求的数据,并且判断是否是Host 保留,不保留就会清空Host 主机头
之后便是利用初始化后的一些信息发起http请求,并返回结果

@Override
@SuppressWarnings("Duplicates")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
   URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);

   String scheme = requestUrl.getScheme();
   if (isAlreadyRouted(exchange)
         || (!"http".equals(scheme) && !"https".equals(scheme))) {
      return chain.filter(exchange);
   }
   setAlreadyRouted(exchange);

   ServerHttpRequest request = exchange.getRequest();

   final HttpMethod method = HttpMethod.valueOf(request.getMethodValue());
   final String url = requestUrl.toASCIIString();

   HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);

   final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
   filtered.forEach(httpHeaders::set);

   boolean preserveHost = exchange
         .getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);

   Flux<HttpClientResponse> responseFlux = this.httpClient.headers(headers -> {
      headers.add(httpHeaders);
      if (preserveHost) {
         String host = request.getHeaders().getFirst(HttpHeaders.HOST);
         headers.add(HttpHeaders.HOST, host);
      }
      else {
         
         headers.remove(HttpHeaders.HOST);
      }
   }).request(method).uri(url).send((req, nettyOutbound) -> {
      if (log.isTraceEnabled()) {
         nettyOutbound.withConnection(connection -> log.trace(
               "outbound route: " + connection.channel().id().asShortText()
                     + ", inbound: " + exchange.getLogPrefix()));
      }
      return nettyOutbound.send(request.getBody()
            .map(dataBuffer -> ((NettyDataBuffer) dataBuffer).getNativeBuffer()));
   }).responseConnection((res, connection) -> {

      
      
      
      exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
      exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection);

      ServerHttpResponse response = exchange.getResponse();
      
      HttpHeaders headers = new HttpHeaders();

      res.responseHeaders()
            .forEach(entry -> headers.add(entry.getKey(), entry.getValue()));

      String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE);
      if (StringUtils.hasLength(contentTypeValue)) {
         exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR,
               contentTypeValue);
      }

      HttpStatus status = HttpStatus.resolve(res.status().code());
      if (status != null) {
         response.setStatusCode(status);
      }
      else if (response instanceof AbstractServerHttpResponse) {
         
         ((AbstractServerHttpResponse) response)
               .setStatusCodeValue(res.status().code());
      }
      else {
         
         throw new IllegalStateException("Unable to set status code on response: "
               + res.status().code() + ", " + response.getClass());
      }

      
      
      HttpHeaders filteredResponseHeaders = HttpHeadersFilter
            .filter(getHeadersFilters(), headers, exchange, Type.RESPONSE);

      if (!filteredResponseHeaders.containsKey(HttpHeaders.TRANSFER_ENCODING)
            && filteredResponseHeaders.containsKey(HttpHeaders.CONTENT_LENGTH)) {
         
         
         
         
         response.getHeaders().remove(HttpHeaders.TRANSFER_ENCODING);
      }

      exchange.getAttributes().put(CLIENT_RESPONSE_HEADER_NAMES,
            filteredResponseHeaders.keySet());

      response.getHeaders().putAll(filteredResponseHeaders);

      return Mono.just(res);
   });

   if (properties.getResponseTimeout() != null) {
      responseFlux = responseFlux.timeout(properties.getResponseTimeout(),
            Mono.error(new TimeoutException("Response took longer than timeout: "
                  + properties.getResponseTimeout())))
            .onErrorMap(TimeoutException.class,
                  th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT,
                        th.getMessage(), th));
   }

   return responseFlux.then(chain.filter(exchange));
}