Although there has been zuul serving gateway (in this case zuul1), by itself or, in other words still blocking the way to achieve synchronization servlet-based implementation. On its own terms of its most fundamental drawback is this again. And the benefits brought by a non-blocking self-evident, efficient use of resources and to improve the throughput of the thread, based on this Spring the first to come up with the killer weapon for web-based, right, is webflux. The Gateway itself is based on webflux basis to achieve. After all, spring launch of the technology, of course, to promote the thing. But on the domestic software companies to stabilize and choose a conservative, and therefore the breadth of the technology itself is still waiting to see me.
1. Gateway Quick Start
Add dependence:
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
Please note here, springcloud-based environment netty Gateway is running, servlet container environment to build or run it as a war of words package is not allowed, so no need to add spring-boot-starter-web in which the project . There are the gateway among three important elements they are:
- Route route is the core element, which defines the ID, a target the URI, the filter set of a set of predicates, and if the polymerization Predicate returns true if the matching route
- Predicate Predicate based java8 function interface, the input parameter types ServerWebExchange, its role is to allow developers to matching rules according to the current http request, for example, http request header, the request time and the like, will determine which of the matching routing execution
- Filter as GatewayFilter, which is constructed from special plants, may request the route change before and after the http requests and responses in a lower layer through Filter
We edit application.yaml, is defined as follows:
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: before_route
uri: http://www.baidu.com
predicates:
- Path=/baidu
server:
port: 8088
At this time when we visit path contains / baidu's, gateway will help us forward to Baidu page
2. Workflow
Here I posted a picture of the official website:
Here I would like to combine the source code to explain the process, and there are a key category, called RoutePredicateHandlerMapping, we can see that this class has the following characteristics:
public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
// ....省略部分代码
@Override
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) + "]");
}
})));
}
//...省略部分代码
}
- Such inherited the AbstractHandlerMapping, note here is that which is available under the handlermapping webflux reactive package, and is equivalent to the webmvc HandlerMapping, its role is to map the request to find the corresponding handler processing.
- The key here is to first deal with finding the right route, the key way for lookupRoute ():
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
return this.routeLocator.getRoutes()
// individually filter routes so that filterWhen error delaying is not a
// problem
.concatMap(route -> Mono.just(route).filterWhen(r -> {
// add the current route we are testing
exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
return r.getPredicate().apply(exchange);
})
// instead of immediately stopping main flux due to error, log and
// swallow it
.doOnError(e -> logger.error(
"Error applying predicate for route: " + route.getId(),
e))
.onErrorResume(e -> Mono.empty()))
// .defaultIfEmpty() put a static Route not found
// or .switchIfEmpty()
// .switchIfEmpty(Mono.<Route>empty().log("noroute"))
.next()
// TODO: error handling
.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("Route matched: " + route.getId());
}
validateRoute(route, exchange);
return route;
});
/*
* TODO: trace logging if (logger.isTraceEnabled()) {
* logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); }
*/
}
- Which functions as an interface RouteLocator is to get Route definition, have the relevant configuration GatewayAutoConfiguaration, everyone can own inspection:
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
List<GatewayFilterFactory> GatewayFilters,
List<RoutePredicateFactory> predicates,
RouteDefinitionLocator routeDefinitionLocator,
@Qualifier("webFluxConversionService") ConversionService conversionService) {
return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates,
GatewayFilters, properties, conversionService);
}
- Note then add the current route we are testing can be obtained at a conclusion, which is filtered off Route suitable conditions according to a statement of Predicate
Finally got FilteringWebHandler as its return value, this class is the true sense of a processing request class that implements an interface WebHandler webflux provided:
public class FilteringWebHandler the implements WebHandler {//.....省略其它代码 @Override public Mono<Void> handle(ServerWebExchange exchange) { //拿到当前的route Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR); //获取所有的gatewayFilter List<GatewayFilter> gatewayFilters = route.getFilters(); //获取全局过滤器 List<GatewayFilter> combined = new ArrayList<>(this.globalFilters); combined.addAll(gatewayFilters); // TODO: needed or cached? AnnotationAwareOrderComparator.sort(combined); if (logger.isDebugEnabled()) { logger.debug("Sorted gatewayFilterFactories: " + combined); } //交给默认的过滤器链执行所有的过滤操作 return new DefaultGatewayFilterChain(combined).filter(exchange); } //....省略其它代码 }
Here you can see its practical approach is to delegate processing request operation of the filter chain
3. Predicate
Spring Cloud Gateway includes many built-Predicate Factory. Predicate match all the different attributes of the HTTP request. If you configure multiple class Predicate, you must meet all of the predicate can, the official online list of built-Predicate, I do not make too many instructions, please refer to: address , predicate implementations can be org.springframework.cloud.gateway.handler.predicate
found in the package.
3.1, custom Predicate
First change it application.yaml configuration:
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: before_route
uri: http://www.baidu.com
predicates:
- Number=1
The default naming convention: Name RoutePredicateFactory, where we can see the following code to resolve the name Predicate rules of the code NameUtils them:
public static String normalizeRoutePredicateName(
Class<? extends RoutePredicateFactory> clazz) {
return removeGarbage(clazz.getSimpleName()
.replace(RoutePredicateFactory.class.getSimpleName(), ""));
}
So here we set the corresponding NumberRoutePredicateFactory in accordance with the above rules, the code is as follows:
@Component
public class NumberRoutePredicateFactory extends AbstractRoutePredicateFactory<NumberRoutePredicateFactory.Config> {
public NumberRoutePredicateFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("number");
}
@Override
public ShortcutType shortcutType() {
return ShortcutType.GATHER_LIST;
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
String number = serverWebExchange.getRequest().getQueryParams().getFirst("number");
return config.number == Integer.parseInt(number);
}
};
}
public static class Config {
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
}
- This class can inherit AbstractRoutePredicateFactory, at the same time you need to register for the spring of Bean
- Which according to the specification in such terms to define an inner class, the role of the class for encapsulating application.yaml configuration, Number = 1 will be packaged in accordance with the configuration rules, as determined by the following:
- ShortcutType, the value is an enumerated type, respectively,
- DEFAULT: turn the assignment in accordance with the order shortcutFieldOrder
- GATHER_LIST: shortcutFiledOrder only have one value, if there is more than makes up a set of parameter
- GATHER_LIST_TAIL_FLAG: shortcutFiledOrder only two values, wherein a final value of true or false, the remaining values into a first set value paid
- shortcutFieldOrder, this value determines the attribute Config configured, the configuration parameters are the attributes which are packaged into
- ShortcutType, the value is an enumerated type, respectively,
4. Filter
The filter can be divided into Gateway (GlobalFilter) filter and the global common filter, the filter may be changed before and after the routing requests and responses to the proxy service. Here I will list two common filter used as a reference for everyone:
4.1 load balancing to achieve
And zuul similar, Gateway can be used as load-balancing server, the processing load balancing is the key to integration with the Ribbon, then the Gateway uses GlobalFilter be implemented, its implementation class is LoadBalancerClientFilter:
public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
protected final LoadBalancerClient loadBalancer;
private LoadBalancerProperties properties;
//....
@Override
@SuppressWarnings("Duplicates")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// preserve the original url
addOriginalRequestUrl(exchange, url);
log.trace("LoadBalancerClientFilter url before: " + url);
//选择一个服务实例
final ServiceInstance instance = choose(exchange);
if (instance == null) {
throw NotFoundException.create(properties.isUse404(),
"Unable to find instance for " + url.getHost());
}
URI uri = exchange.getRequest().getURI();
// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
// if the loadbalancer doesn't provide one.
//判断协议类型
String overrideScheme = instance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
//重构uri地址
URI requestUrl = loadBalancer.reconstructURI(
new DelegatingServiceInstance(instance, overrideScheme), uri);
//...
return chain.filter(exchange);
}
}
Here we can see here that it is based on the Spring-Cloud-Commons specification in the LoadBalanceClient package implementation.
4.2, integrated Hystrix
Gateway and Hystrix same can also be integrated, and there's a key class is HystrixGatewayFilterFactory, the key there is RouteHystrixCommand class inherits HystrixObservableCommand:
@Override
protected Observable<Void> construct() {
// 执行过滤器链
return RxReactiveStreams.toObservable(this.chain.filter(exchange));//1
}
@Override
protected Observable<Void> resumeWithFallback() {
if (this.fallbackUri == null) {
return super.resumeWithFallback();
}
// TODO: copied from RouteToRequestUrlFilter
URI uri = exchange.getRequest().getURI();
// TODO: assume always?
boolean encoded = containsEncodedParts(uri);
URI requestUrl = UriComponentsBuilder.fromUri(uri).host(null).port(null)
.uri(this.fallbackUri).scheme(null).build(encoded).toUri();//2
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
addExceptionDetails();
ServerHttpRequest request = this.exchange.getRequest().mutate()
.uri(requestUrl).build();
ServerWebExchange mutated = exchange.mutate().request(request).build();
return RxReactiveStreams.toObservable(getDispatcherHandler().handle(mutated));//3
}
- In the code will be executed at the filter chain 1, the code is written here will be unified together with the protection hystrix
- 2 is again at the code to perform the method of rollback, a rollback request address constructed according fallbackUri
- Rollback processing code acquired address controller WebFlux total at three DispatcherHandler
5, Service Discovery
For Gateway Service Discovery is also a very important element, Gateway where the definition of a core interface is called: RouteDefinitionLocator, this interface is used to define the Route of acquiring, service discovery mechanism implements this interface:
public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
//....省略部分代码
return Flux.fromIterable(discoveryClient.getServices())//获取所有服务
.map(discoveryClient::getInstances) //映射转换所有服务实例
.filter(instances -> !instances.isEmpty()) //过滤出不为空的服务实例
.map(instances -> instances.get(0)).filter(includePredicate)//根据properites里的include表达式过滤实例
.map(instance -> {
/*
构建Route的定义
*/
String serviceId = instance.getServiceId();
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(this.routeIdPrefix + serviceId);
String uri = urlExpr.getValue(evalCtxt, instance, String.class);
routeDefinition.setUri(URI.create(uri));
final ServiceInstance instanceForEval = new DelegatingServiceInstance(
instance, properties);
//添加Predicate
for (PredicateDefinition original : this.properties.getPredicates()) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs()
.entrySet()) {
String value = getValueFromExpr(evalCtxt, parser,
instanceForEval, entry);
predicate.addArg(entry.getKey(), value);
}
routeDefinition.getPredicates().add(predicate);
}
//添加filter
for (FilterDefinition original : this.properties.getFilters()) {
FilterDefinition filter = new FilterDefinition();
filter.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs()
.entrySet()) {
String value = getValueFromExpr(evalCtxt, parser,
instanceForEval, entry);
filter.addArg(entry.getKey(), value);
}
routeDefinition.getFilters().add(filter);
}
return routeDefinition;
});
}
}
From this we can see, there is the use of DiscoveryClient get all service instances and each instance to build a Route, but before that, in the class GatewayDiscoveryClientAutoConfiguration automatic assembly's already configured with the default Predicate Filter, it will help advance we configure the default Predicate and Filter:
public static List<PredicateDefinition> initPredicates() {
ArrayList<PredicateDefinition> definitions = new ArrayList<>();
// TODO: add a predicate that matches the url at /serviceId?
// add a predicate that matches the url at /serviceId/**
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class));
predicate.addArg(PATTERN_KEY, "'/'+serviceId+'/**'");
definitions.add(predicate);
return definitions;
}
public static List<FilterDefinition> initFilters() {
ArrayList<FilterDefinition> definitions = new ArrayList<>();
// add a filter that removes /serviceId by default
FilterDefinition filter = new FilterDefinition();
filter.setName(normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
String regex = "'/' + serviceId + '/(?<remaining>.*)'";
String replacement = "'/${remaining}'";
filter.addArg(REGEXP_KEY, regex);
filter.addArg(REPLACEMENT_KEY, replacement);
definitions.add(filter);
return definitions;
}
There is constructed before the main part of Path = / serviceId / Predicate and routed to a corresponding service ** removed filter according to ServiceId ServiceId
6, summed up
According to the above description, I just selected two typical meaning of Predicate and Filter code description, because the official website did not specify a custom Predicate, I am here simply to write a simple example, the example of a custom Filter can refer to the official website address :
You should Tucao about official when TODO supplement can complete it?
Gateway is based Webflux implemented to process user requests it via the expansion HandlerMapping WebHandler, first by targeting Predicate Router then FilterChain service through the filtering process to the final positioning of the lower layer. At the same time the official has provided us with many Prdicate Filter, such as current limiting. From this point, it features more powerful than the zuul it, zuul in some service discovery, open circuit protection, Gateway respectively implemented by GlobalFilter and Filter.
Finally, as to what extent Gateway can spread to, or can not also eventually become a unified standard gateway, that I can no longer be guaranteed here, then it is time to prove it.