WebClientHttpRoutingFilter前言
在Spring Cloud Gateway源码解析-05-请求处理之FilteringWebHandler中已经讲解过在获取过在DispatcherHandler
通过RoutePredicateHandlerMapping
获取到FilteringWebHandler
后会调用FilteringWebHandler
的handle
方法。并且FilteringWebHandler
中已经组合了当前路由的Filter和SCG全局的Filter。
DefaultGatewayFilterChain
在FilteringWebHandler#handle
方法中会创建DefaultGatewayFilterChain去链式的执行所有的Filter。
private static class DefaultGatewayFilterChain implements GatewayFilterChain {
private final int index;
private final List<GatewayFilter> filters;
DefaultGatewayFilterChain(List<GatewayFilter> filters) {
this.filters = filters;
//在FilteringWebHandler的handle方法中初始化时设置当前应调用的过滤器下标为0,也就是第一个
this.index = 0;
}
//在filter方法中调用,传入过滤器链和需要执行的过滤器index
private DefaultGatewayFilterChain(DefaultGatewayFilterChain parent, int index) {
this.filters = parent.getFilters();
this.index = index;
}
public List<GatewayFilter> getFilters() {
return filters;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange) {
return Mono.defer(() -> {
//判断是否已经执行过所有的过滤器
if (this.index < filters.size()) {
//取出当前需执行的过滤器
GatewayFilter filter = filters.get(this.index);
//每个Filter都创建一个DefaultGatewayFilterChain去执行
DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this,
this.index + 1);
return filter.filter(exchange, chain);
} else {
return Mono.empty(); // complete
}
});
}
}
Filter
SCG内置了许多Filter,分为GatewayFilter和GlobalFilter,GatewayFilter和Predicate类似,都是通过Factory创建的,通过配置作用于每个路由,而GlobalFilter是作用于所有的请求。当请求匹配到对应路由的时候,会将GlobalFilter和路由绑定的GatewayFilter合并到一起,所有的Filter都实现了org.springframework.core.Ordered
接口,因此会根据Filtr的order进行排序。Filter中包括了真正发送请求的Filter,因此又可分为前置Filter和后置Filter。
补充
GatewayFilterFactory
并没有实现Ordered
接口,其实在RouteDefinitionRouteLocator
组装Filter的时候进行了封装OrderedGatewayFilter
,OrderedGatewayFilter实现了Ordered接口,会在初始化时设置order。
List<GatewayFilter> loadGatewayFilters(String id,
List<FilterDefinition> filterDefinitions) {
//生成GatewayFilter
GatewayFilter gatewayFilter = factory.apply(configuration);
if (gatewayFilter instanceof Ordered) {
ordered.add(gatewayFilter);
} else {
//如果没有实现Ordered接口,则根据遍历的顺序排序
ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1));
}
}
public class OrderedGatewayFilter implements GatewayFilter, Ordered {
private final GatewayFilter delegate;
private final int order;
public OrderedGatewayFilter(GatewayFilter delegate, int order) {
this.delegate = delegate;
this.order = order;
}
}
GlobalFilter
通过下边这张图可以看到所有的GlobalFilter及执行顺序。
GlobalFilter | order值 |
---|---|
RemoveCachedBodyFilter | Integer.MIN_VALUE; |
AdaptCachedBodyGlobalFilter | Integer.MIN_VALUE + 1000 |
NettyWriteResponseFilter | -1 |
ForwardPathFilter | 0 |
GatewayMetricsFilter | 0 |
RouteToRequestUrlFilter | 1000 |
LoadBalancerClientFilter | 10100 |
WebsocketRoutingFilter | Integer.MAX_VALUE -1 |
NettyRoutingFilter | Integer.MAX_VALUE |
ForwardRoutingFilter | Integer.MAX_VALUE |
当order值相同时,根据加载的先后顺序执行。NettyRoutingFilter
先于ForwardRoutingFilter
加载。
RemoveCachedBodyFilter
清除上下文中的body缓存。是配合AdaptCachedBodyGlobalFilter
使用的,AdaptCachedBodyGlobalFilter
会将请求body放入缓存,等所有的filter执行完后再从上下文中将body缓存删除。
public class RemoveCachedBodyFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//调用下一个filter
return chain.filter(exchange).doFinally(s -> {//doFinally表示最终执行的操作
//清除缓存
Object attribute = exchange.getAttributes().remove(CACHED_REQUEST_BODY_ATTR);
if (attribute != null && attribute instanceof PooledDataBuffer) {
PooledDataBuffer dataBuffer = (PooledDataBuffer) attribute;
if (dataBuffer.isAllocated()) {
if (log.isTraceEnabled()) {
log.trace("releasing cached body in exchange attribute");
}
dataBuffer.release();
}
}
});
}
}
AdaptCachedBodyGlobalFilter
用来添加缓存的Filter,具体看下面的代码注释即可。
public class AdaptCachedBodyGlobalFilter
implements GlobalFilter, Ordered, ApplicationListener<EnableBodyCachingEvent> {
/**
* 当我们配置了RetryGatewayFilterFactory重试时,会在执行重试逻辑时发布EnableBodyCachingEvent,此处会监听到该事件
* @param event
*/
@Override
public void onApplicationEvent(EnableBodyCachingEvent event) {
this.routesToCache.putIfAbsent(event.getRouteId(), true);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// the cached ServerHttpRequest is used when the ServerWebExchange can not be
// mutated, for example, during a predicate where the body is read, but still
// needs to be cached.
//从上下文获取CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR,CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR通过名称可以看出来就是请求的封装
//当使用了ReadBodyRoutePredicateFactory时,test时会将请求body放入上下文中,此处的缓存主要是为了后边不用再序列化
//如果不为空,表示请求和请求体都已经缓存了,则通过缓存的request构建一个上下文请求
ServerHttpRequest cachedRequest = exchange
.getAttributeOrDefault(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR, null);
if (cachedRequest != null) {
exchange.getAttributes().remove(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR);
return chain.filter(exchange.mutate().request(cachedRequest).build());
}
//如果上边没有从上下文中获取到缓存,则获取CACHED_REQUEST_BODY_ATTR
// CACHED_REQUEST_BODY_ATTR是请求体的缓存,此处的缓存可能是想让我们通过自定义Predicate或者Filter的方式在此Filter之前将body先序列化缓存
DataBuffer body = exchange.getAttributeOrDefault(CACHED_REQUEST_BODY_ATTR, null);
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
//此处判断body是否为空或者routesToCache是否包含当前路由ID,表示当前请求已经被缓存过
//routesToCache在#onApplicationEvent中可能会put值
if (body != null || !this.routesToCache.containsKey(route.getId())) {
return chain.filter(exchange);
}
//如果上边的条件都不满足,则会将当前请求Body放到缓存中
return ServerWebExchangeUtils.cacheRequestBody(exchange, (serverHttpRequest) -> {
// don't mutate and build if same request object
//如果是同一个请求,则直接执行Filter逻辑
if (serverHttpRequest == exchange.getRequest()) {
return chain.filter(exchange);
}
//否则,通过新的请求构建一个请求上下文
return chain.filter(exchange.mutate().request(serverHttpRequest).build());
});
}
}
NettyWriteResponseFilter
NettyWriteResponseFilter
在所有Filter之后运行(除了RemoveCachedBodyFilter
),用于将服务的响应数据返回到客户端,与之类似的有WebClientWriteResponseFilter,属于测试版本,并未启用
。NettyWriteResponseFilter
会获取上下文中的CLIENT_RESPONSE_CONN_ATTR
,这个Attr会在NettyRoutingFilter
中放入,后边会解释NettyRoutingFilter
的作用。
public class NettyWriteResponseFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange)
.doOnError(throwable -> cleanup(exchange))
.then(Mono.defer(() -> {
/**
* 从上下文中获取CLIENT_RESPONSE_CONN_ATTR,Connection是对NettyChannel的封装
* CLIENT_RESPONSE_CONN_ATTR是在{@link NettyRoutingFilter#filter}中放入的
*/
Connection connection = exchange.getAttribute(CLIENT_RESPONSE_CONN_ATTR);
if (connection == null) {
return Mono.empty();
}
if (log.isTraceEnabled()) {
log.trace("NettyWriteResponseFilter start inbound: "
+ connection.channel().id().asShortText() + ", outbound: "
+ exchange.getLogPrefix());
}
ServerHttpResponse response = exchange.getResponse();
// TODO: needed?
/**
*将byteBuf转换为DateBuff,
* 因为{@link org.springframework.http.ReactiveHttpOutputMessage#writeWith(Publisher)}需要DateBuff类型的
*/
final Flux<DataBuffer> body = connection
.inbound()
.receive()
.retain()
.map(byteBuf -> wrap(byteBuf, response));
MediaType contentType = null;
try {
contentType = response.getHeaders().getContentType();
}
catch (Exception e) {
if (log.isTraceEnabled()) {
log.trace("invalid media type", e);
}
}
//将NettyResponse写回给客户端
return (isStreamingMediaType(contentType)
? response.writeAndFlushWith(body.map(Flux::just))
: response.writeWith(body));
})).doOnCancel(() -> cleanup(exchange));
// @formatter:on
}
}
ForwardPathFilter
用来处理Forward URI,对应ForwardRoutingFilter
来转发请求。
public class ForwardPathFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取路由Route
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
//获取请求URI
URI routeUri = route.getUri();
String scheme = routeUri.getScheme();
//如果请求已经被处理过或者uri的scheme不是forward,则不处理
//可以通过自定义过滤器来设置GATEWAY_ALREADY_ROUTED_ATTR为true从而使Filter不起作用
if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {
return chain.filter(exchange);
}
//替换请求path重新构建path
exchange = exchange.mutate()
.request(exchange.getRequest().mutate().path(routeUri.getPath()).build())
.build();
return chain.filter(exchange);
}
}
}
这里提一下可以通过调用ServerWebExchangeUtils#setAlreadyRouted
禁用掉部分Filter,可禁用的Filter如下。
GatewayMetricsFilter
网关指标监控过滤器,需要添加spring-boot-starter-actuator依赖,可通过spring.cloud.gateway.metrics.enabled=true/false
进行配置,默认为开启状态。可以通过/actuator/metrics/gateway.requests
来访问查看。
可提供如下数据:
- routeId
- routUrI
- outcome:结果,按HttpStatus.Series分类。
- INFORMATIONAL
- SUCCESSFUL
- REDIRECTION
- CLIENT_ERROR
- SERVER_ERROR
- statue**:状态**
- httpStatusCode:状态码
- httpMethod:请求方式
GatewayMetricsFilter
不是重点,我们就不做详细的分析了,感兴趣的可以自己分析下!
RouteToRequestUrlFilter
用于根据RouteUri生成真正请求的URL,并放入请求上下文中供后边Filter使用
public class RouteToRequestUrlFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
//判断上下中是否有GATEWAY_ROUTE_ATTR,在RoutePredicateHandlerMapping中放入的
//如果没有则不执行
if (route == null) {
return chain.filter(exchange);
}
log.trace("RouteToRequestUrlFilter start");
//获取请求的URI
URI uri = exchange.getRequest().getURI();
//判断是否包含编码的部分,如%
boolean encoded = containsEncodedParts(uri);
//获取Route的uri
URI routeUri = route.getUri();
//判断是否为其他类型的协议 如:lb,则会将lb去掉
if (hasAnotherScheme(routeUri)) {
// this is a special url, save scheme to special attribute
// replace routeUri with schemeSpecificPart
//将当前请求的schema放入上下文中
exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR,
routeUri.getScheme());
routeUri = URI.create(routeUri.getSchemeSpecificPart());
}
//如果RouteUri以lb开头,必须请求中带有host
if ("lb".equalsIgnoreCase(routeUri.getScheme()) && routeUri.getHost() == null) {
// Load balanced URIs should always have a host. If the host is null it is
// most
// likely because the host name was invalid (for example included an
// underscore)
throw new IllegalStateException("Invalid host: " + routeUri.toString());
}
//生成RequestURL,并放入上下文中
//此处生成的URL的Path最终会以请求的Path为主,会覆盖真正的RouteUri,
// 例如RouteUri为http://localhost:8088/api/hello,请求的URI为http://localhost:8080/api,
// 那此处生成的URL为http://localhost:8080/api
URI mergedUrl = UriComponentsBuilder.fromUri(uri)
// .uri(routeUri)
.scheme(routeUri.getScheme()).host(routeUri.getHost())
.port(routeUri.getPort()).build(encoded).toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);
return chain.filter(exchange);
}
}
LoadBalancerClientFilter和ReactiveLoadBalancerClientFilter
WebsocketRoutingFilter
处理websocket请求,当请求上下文中的GATEWAY_REQUEST_URL_ATTR
的URL中的协议(schema)为ws或者wss该Filter生效,使用Spring的WebSocket对请求进行转发。同时可以进行负载均衡,通过在Route的URI配置前边加上lb:
生效。
配置
spring:
cloud:
gateway:
routes:
- id: hello_route
uri: ws://httpbin或者wss://httpbin 也可在前边加上lb:表示需要负载均衡
predicates:
- Path=/api
filter方法
public class WebsocketRoutingFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
changeSchemeIfIsWebSocketUpgrade(exchange);
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
//只处理协议为ws或者wss的
if (isAlreadyRouted(exchange)
|| (!"ws".equals(scheme) && !"wss".equals(scheme))) {
return chain.filter(exchange);
}
//设置为已被处理,后边的NettyRoutingFilter或者WebClientHttpRoutingFilter则不会执行
setAlreadyRouted(exchange);
HttpHeaders headers = exchange.getRequest().getHeaders();
HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);
List<String> protocols = headers.get(SEC_WEBSOCKET_PROTOCOL);
if (protocols != null) {
protocols = headers.get(SEC_WEBSOCKET_PROTOCOL).stream().flatMap(
header -> Arrays.stream(commaDelimitedListToStringArray(header)))
.map(String::trim).collect(Collectors.toList());
}
/**
* 通过{@link HandshakeWebSocketService}去转发的请求
*/
return this.webSocketService.handleRequest(exchange, new ProxyWebSocketHandler(
requestUrl, this.webSocketClient, filtered, protocols));
}
}
NettyRoutingFilter
NettyRoutingFilter
处理schema为http/https
的请求,使用基于Netty HttpClient请求后端的服务,上边讲到的NettyWriteResponseFilter
用来处理NettyRoutingFilter
请求后端获得的响应,将响应写回给客户端。
同时SCG还定义了WebClientHttpRoutingFilter
,于NettyRoutingFilter
类似,区别在于没有使用Netty去做请求转发的代理。NettyRoutingFilter
中会将请求的响应放入上下文中,供NettyWriteResponseFilter
使用。
请求超时时间:通过查看getHttpClient
方法,可看到通route.getMetadata().get(``connect-timeout``);
获取我们配置的路由的请求超时时间。
响应超时时间:同请求超时时间。代码见getResponseTimeout
方法。
**
画了一张大概的图描述NettyRoutingFilter
和NettyWriteResponseFilter
的关系
public class NettyRoutingFilter implements GlobalFilter, Ordered {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/**
* 从上下文中获取在{@link RouteToRequestUrlFilter}中放入的请求URL
*/
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
//获取请求的协议
String scheme = requestUrl.getScheme();
//如果该请求已经被处理过,或者请求协议不是http/https,则不处理
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());
//获取请求的URI
final String url = requestUrl.toASCIIString();
//执行请求头Filter,如ForwardedHeadersFilter、RemoveHopByHopHeadersFilter、XForwardedHeadersFilter
HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);
final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
//基于filter过后的请求头创建Http请求头
filtered.forEach(httpHeaders::set);
boolean preserveHost = exchange
.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);
//获取路由
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
//创建HttpClient
Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange)
.headers(headers -> {
//添加请求头
headers.add(httpHeaders);
// Will either be set below, or later by Netty
//移除HOST
headers.remove(HttpHeaders.HOST);
if (preserveHost) {//判断是否需要增加HOST
String host = request.getHeaders().getFirst(HttpHeaders.HOST);
headers.add(HttpHeaders.HOST, 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(this::getByteBuf));
}).responseConnection((res, connection) -> {
// Defer committing the response until all route filters have run
// Put client response as ServerWebExchange attribute and write
// response later NettyWriteResponseFilter
//将调用真实服务返回的Response放入上下文,但NettyWriteResponseFilter中也没有用
exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
//将Netty Channle放入上下文供NettyWriteResponseFilter使用
exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection);
...................省略部分代码..................
}
//获取响应超时时间
Duration responseTimeout = getResponseTimeout(route);
if (responseTimeout != null) {
//设置获取响应超时时间
responseFlux = responseFlux
.timeout(responseTimeout, Mono.error(new TimeoutException(
"Response took longer than timeout: " + responseTimeout)))
.onErrorMap(TimeoutException.class,
th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT,
th.getMessage(), th));
}
return responseFlux.then(chain.filter(exchange));
}
ForwardRoutingFilter
用来处理forward协议的请求,将ForwardPathFilter
构建的新的Request发送给DispatcherHandler处理。
public class ForwardRoutingFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取RouteToRequestUrlFilter中生成的请求URL
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
//判定是否已经处理过或者请求协议为forward
if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {
return chain.filter(exchange);
}
// TODO: translate url?
if (log.isTraceEnabled()) {
log.trace("Forwarding to URI: " + requestUrl);
}
//发送给DispatcherHander处理
return this.getDispatcherHandler().handle(exchange);
}
}