In-depth understanding of the Gateway SpringCloud

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.predicatefound 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

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.

Guess you like

Origin www.cnblogs.com/niechen/p/11672630.html