谓词 After · SpringCloud微服务实战 · 看云
1. 谓词 After
导航
下面我们开始第一个谓词After ,该谓词后面需要拼接一个参数,即日期时间。该谓词匹配在指定日期时间之后发生的请求。匹配不到会返回404
本节代码地址
1.1 新建项目
为了演示方便,我们新建一个项目,并且后面爱他问题也使用这个项目,我们使用多profile 构建项目,每个profile 是一张类型。
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 启动项目
浏览器或Postman 输入localhost:8699/user/1
将时间调大
- After=2021-01-08T18:30:11.965+08:00[Asia/Shanghai]
再次测试可以看到404,因为没有转发
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));
}



