1. 基础
1.1 Features
1. Built on Spring Framework 5, Project Reactor and Spring Boot 2.0
Able to match routes on any request attribute.
2. Predicates and filters are specific to routes.
3. Circuit Breaker integration.
4. Spring Cloud DiscoveryClient integration
5. Easy to write Predicates and Filters
6. Request Rate Limiting
7. Path Rewriting
1.2 使用方法
- 在配置文件中设置
- 在代码中设置,如下
@SpringBootApplication
public class DemogatewayApplication {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/get")
.uri("http://httpbin.org"))
.route("host_route", r -> r.host("*.myhost.org")
.uri("http://httpbin.org"))
.route("rewrite_route", r -> r.host("*.rewrite.org")
.filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}"))
.uri("http://httpbin.org"))
.route("hystrix_route", r -> r.host("*.hystrix.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd")))
.uri("http://httpbin.org"))
.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
.uri("http://httpbin.org"))
.route("limit_route", r -> r
.host("*.limited.org").and().path("/anything/**")
.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
.uri("http://httpbin.org"))
.build();
}
}
1.3 处理流程
1.4 配置启动参数
相比zuul,不需要在Application上家EnableZuulProxy了,只需要在配置中加上
spring.cloud.gateway.enabled=true # 默认为true spring.cloud.gateway.discovery.locator.enabled=true
2. 如何工作的
2.1 初始化
GatewayAutoConfiguration自动配置,当spring.cloud.gateway.enabled未设置为false时就会启动。
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class,
WebFluxAutoConfiguration.class })
@AutoConfigureAfter({ GatewayLoadBalancerClientAutoConfiguration.class,
GatewayClassPathWarningAutoConfiguration.class })
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {
//remove other code
}
在这里会处理化很多bean
- DefinitionLocator
- PropertiesRouteDefinitionLocator
- InMemoryRouteDefinitionRepository
- RouteDefinitionLocator
- GlobalFilter
- AdaptCachedBodyGlobalFilter
- RemoveCachedBodyFilter
- RouteToRequestUrlFilter
- ForwardRoutingFilter
- ForwardPathFilter
- Websocket
- WebSocketService
- WebsocketRoutingFilter
- Predicate (支持的Predicate类型)
- AfterRoutePredicateFactory
- BeforeRoutePredicateFactory
- BetweenRoutePredicateFactory
- CookieRoutePredicateFactory
- HeaderRoutePredicateFactory
- HostRoutePredicateFactory
- MethodRoutePredicateFactory
- PathRoutePredicateFactory
- QueryRoutePredicateFactory
- ReadBodyRoutePredicateFactory
- RemoteAddrRoutePredicateFactory
- WeightRoutePredicateFactory
- CloudFoundryRouteServiceRoutePredicateFactory
- GatewayFilter Factory
- AddRequestHeaderGatewayFilterFactory
- MapRequestHeaderGatewayFilterFactory
- AddRequestParameterGatewayFilterFactory
- AddResponseHeaderGatewayFilterFactory
- ModifyRequestBodyGatewayFilterFactory
- DedupeResponseHeaderGatewayFilterFactory
- ModifyResponseBodyGatewayFilterFactory
- PrefixPathGatewayFilterFactory
- PreserveHostHeaderGatewayFilterFactory
- RedirectToGatewayFilterFactory
- RemoveRequestHeaderGatewayFilterFactory
- RemoveRequestParameterGatewayFilterFactory
- RemoveResponseHeaderGatewayFilterFactory
- RequestRateLimiterGatewayFilterFactory (限流)
- RewritePathGatewayFilterFactory
- RetryGatewayFilterFactory
- SetPathGatewayFilterFactory
- SecureHeadersGatewayFilterFactory
- SetRequestHeaderGatewayFilterFactory
- SetStatusGatewayFilterFactory
- StripPrefixGatewayFilterFactory
- RequestHeaderToRequestUriGatewayFilterFactory
- RequestSizeGatewayFilterFactory
- RequestHeaderSizeGatewayFilterFactory
- 其它
- NettyConfiguration
- HttpClient
- NettyRoutingFilter (重要,实际的HTTP代理处理)
- NettyWriteResponseFilter
- ReactorNettyWebSocketClient
- ReactorNettyRequestUpgradeStrategy
- HystrixConfiguration
- HystrixGatewayFilterFactory
- FallbackHeadersGatewayFilterFactory
- GatewayActuatorConfiguration
- GatewayControllerEndpoint
- GatewayLegacyControllerEndpoint
- NettyConfiguration
2.2 请求处理
2.2.1 入口文件
org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping
RoutePredicateHandlerMapping继承自AbstractHandlerMapping,是spring5.0的HandlerMapping抽象类,需要实现其getHandlerInternal方法。在这个方法里,
- 根据客户端传入的请求,在所有定义好的Route进行查找,获取匹配的Route,如果没有找到则抛出异常。
- 对找到的route,调用FilteringWebHandler进行处理
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
// don't handle requests on management port if set and different than server port
if (this.managementPortType == DIFFERENT && this.managementPort != null
&& exchange.getRequest().getURI().getPort() == this.managementPort) {
return Mono.empty();
}
exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());
return lookupRoute(exchange)
// .log("route-predicate-handler-mapping", Level.FINER) //name this
.flatMap((Function<Route, Mono<?>>) r -> {
exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
if (logger.isDebugEnabled()) {
logger.debug(
"Mapping [" + getExchangeDesc(exchange) + "] to " + r);
}
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
return Mono.just(webHandler);
}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
if (logger.isTraceEnabled()) {
logger.trace("No RouteDefinition found for ["
+ getExchangeDesc(exchange) + "]");
}
})));
}
FilteringWebHandler继承自WebHanlder,需要实现其handle方法。 handle方法中,会对所有的GatewayFilter和GlobalFilter进行处理。GlobalFilter会包装成GatewayFilter一起处理,按照order进行排序。
public class FilteringWebHandler implements WebHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
List<GatewayFilter> gatewayFilters = route.getFilters();
List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
combined.addAll(gatewayFilters);
AnnotationAwareOrderComparator.sort(combined);
return new DefaultGatewayFilterChain(combined).filter(exchange);
}
}
DefaultGatewayFilterChain的filter方法 在处理完成一个filter后,会继续调用下一个,直到所有的filter处理完成。
private static class DefaultGatewayFilterChain implements GatewayFilterChain {
@Override
public Mono<Void> filter(ServerWebExchange exchange) {
return Mono.defer(() -> {
if (this.index < filters.size()) {
GatewayFilter filter = filters.get(this.index);
DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this,
this.index + 1);
return filter.filter(exchange, chain);
}
else {
return Mono.empty(); // complete
}
});
}
}
2.2.2 如何发起对内部服务的请求的?
NettyRoutingFilter一个最低优先级的GlobalFilter,也即最后一个执行的Filter。
public class NettyRoutingFilter implements GlobalFilter, Ordered {
//remove other code
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//实际发起远端HTTP请求的地方
//这一部分是使用netty的实现的,也是为什么性能重要原因之一
}
}
以下是filter方法的完整的代码
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);
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange)
.headers(headers -> {
headers.add(httpHeaders);
// Will either be set below, or later by Netty
headers.remove(HttpHeaders.HOST);
if (preserveHost) {
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
exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection);
ServerHttpResponse response = exchange.getResponse();
// put headers and status so filters can modify the response
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);
}
setResponseStatus(res, response);
// make sure headers filters run after setting status so it is
// available in response
HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(
getHeadersFilters(), headers, exchange, Type.RESPONSE);
if (!filteredResponseHeaders
.containsKey(HttpHeaders.TRANSFER_ENCODING)
&& filteredResponseHeaders
.containsKey(HttpHeaders.CONTENT_LENGTH)) {
// It is not valid to have both the transfer-encoding header and
// the content-length header.
// Remove the transfer-encoding header in the response if the
// content-length header is present.
response.getHeaders().remove(HttpHeaders.TRANSFER_ENCODING);
}
exchange.getAttributes().put(CLIENT_RESPONSE_HEADER_NAMES,
filteredResponseHeaders.keySet());
response.getHeaders().putAll(filteredResponseHeaders);
return Mono.just(res);
});
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));
}