Spring Cloud Gateway

The framework version
Spring Boot 2.5.3
Spring Cloud 2020.0.3

Maven rely on

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
Copy the code

concept

Routes: Gateway core concepts that include a route ID, a forwarding address URI, a set of assertions, and a set of Predicate. Filters used to determine whether a request meets the current routing rules: Used to process the current request, which is divided into global filters, and routing filtersCopy the code

implementation

  1. The configuration file
spring:
  cloud:
    gateway:
      routes:
        - id: theia-routes-base
          uri: "http://10.20.23.49:31002"
          predicates:
            - Path=/zhaolw01/01
          filters:
            - SetPath=/
Copy the code

Description:

Id: unique, used to store and update routing information URI: forwarding address Predicates: predicates: predicates: predicates: combines multiple assertion types with filters

  1. Java classes
        @Bean
        public RouteLocator theiaRouteLocator(RouteLocatorBuilder builder) {
            return builder
                    .routes()
                    .route("theiaRoute",r -> r.path("/xiangaoxiong01")
                        .filters(gatewayFilterSpec -> gatewayFilterSpec.setPath("/"))
                        .uri("http://10.20.23.49:31002")
                    )
                    .build();
        }
Copy the code

Description:

RouteLocator: a primary routing object that the Gateway loads and updates to implement dynamic routing

Custom assertion

Through inheritance AbstractRoutePredicateFactory class to quickly implement a custom assertions, need to customize a Config class content, used to receive that. Rewrite the apply method, return a GatewayPredicate type.

@Configuration
@Slf4j
public class TheiaServiceRoutePredicateFactory extends AbstractRoutePredicateFactory<TheiaServiceRoutePredicateFactory.Config> {

    public TheiaServiceRoutePredicateFactory(a) {
        super(Config.class);
    }

    @Bean
    @ConditionalOnEnabledPredicate
    public TheiaServiceRoutePredicateFactory getTheiaServiceRoutePredicateFactory(a){
        return new TheiaServiceRoutePredicateFactory();
    }

    @Override
    public List<String> shortcutFieldOrder(a) {
        return Arrays.asList("patterns");
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        List<String> patterns = config.getPatterns();
        return (GatewayPredicate) serverWebExchange -> {
            ServerHttpRequest request = serverWebExchange.getRequest();
            log.info("Custom assertion: {}", patterns);
            String url = request.getURI().getRawPath();
            return patterns.parallelStream().filter(x -> url.startsWith(x)).count() > 0 ;
        };
    }

    @Validated
    public static class Config {

        private List<String> patterns = new ArrayList<>();

        public List<String> getPatterns(a) {
            return patterns;
        }

        public TheiaServiceRoutePredicateFactory.Config setPatterns(List<String> patterns) {
            this.patterns = patterns;
            return this; }}}Copy the code

Custom filters:

@Configuration
@Slf4j
public class TheiaServiceGatewayFilterFactory extends AbstractGatewayFilterFactory<TheiaServiceGatewayFilterFactory.Config> {

    public TheiaServiceGatewayFilterFactory(a) {
        super(TheiaServiceGatewayFilterFactory.Config.class);
    }

    @Bean
    @ConditionalOnEnabledFilter
    public TheiaServiceGatewayFilterFactory getTheiaServiceGatewayFilterFactory(a) {
        return new TheiaServiceGatewayFilterFactory();
    }

    @Override
    public List<String> shortcutFieldOrder(a) {
        return Arrays.asList("template");
    }


    @Override
    public GatewayFilter apply(Config config) {
        String template = config.getTemplate();
        return (exchange, chain) -> {
            ServerHttpRequest req = exchange.getRequest();
            log.info(Custom filter :{},template);
            String newPath = req.getURI().getRawPath().replaceAll(template,"/");
            ServerHttpRequest request = req.mutate().path(newPath).build();
            return chain.filter(exchange.mutate().request(request).build());
        };
    }

    public static class Config {

        private String template;

        public String getTemplate(a) {
            return template;
        }

        public void setTemplate(String template) {
            this.template = template; }}}Copy the code

AbstractRoutePredicateFactory and AbstractGatewayFilterFactory is the official offer static implementation class, which implements the general part, you just need to write custom apply method, ShortcutFieldOrder is used to handle parameter injection.

The principle of analytic

If you want to use the spring-cloud-gateway routing service, you can use the spring-cloud-gateway routing service. If you want to use the Spring-cloud-gateway routing service, you can use the spring-cloud-gateway routing service.

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayResilience4JCircuitBreakerAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayNoLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration,\
org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration,\
org.springframework.cloud.gateway.config.SimpleUrlHandlerMappingGlobalCorsAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayReactiveOAuth2AutoConfiguration
Copy the code

Can be seen by looking at the GatewayClassPathWarningAutoConfiguration, Gateway is based on the WebFlux implementation

        @Configuration(proxyBeanMethods = false)
   @ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet")
   @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
   protected static class SpringMvcFoundOnClasspathConfiguration {

      public SpringMvcFoundOnClasspathConfiguration(a) {
         throw newMvcFoundOnClasspathException(); }}@Configuration(proxyBeanMethods = false)
   @ConditionalOnMissingClass("org.springframework.web.reactive.DispatcherHandler")
   protected static class WebfluxMissingFromClasspathConfiguration {

      public WebfluxMissingFromClasspathConfiguration(a) {
         log.warn(BORDER + "Spring Webflux is missing from the classpath, "
               + "which is required for Spring Cloud Gateway at this time. "
               + "Please add spring-boot-starter-webflux dependency."+ BORDER); }}Copy the code

The key point is that the DispatcherServlet is implemented based on servlets. An error is reported when the Class is loaded, and a warning exception is reported when no DispatcherHandler exists.

GatewayAutoConfiguration is the core of the GatewayAutoConfiguration. Due to too much content, only a few of the more important beans are loaded:

        @Bean
   @ConditionalOnEnabledPredicate(WeightRoutePredicateFactory.class)
   public WeightCalculatorWebFilter weightCalculatorWebFilter(ConfigurationService configurationService, ObjectProvider
       
         routeLocator)
        {
      return new WeightCalculatorWebFilter(routeLocator, configurationService);
   }
Copy the code

As weight routing load, because WeightCalculatorWebFilter WebFliter is achieved, while the rest of the routing is based on the realization of the HandlerMapping DispatcherHandler.

        @Bean
   public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
      return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment);
   }
Copy the code

RoutePredicateHandlerMapping is the core of the routing handler class, it implements the HandlerMapping interface, will be injected into DispatcherHandler handlerMappings play a role.

Check the RoutePredicateHandlerMapping, core method is as follows:

        @Override
   protectedMono<? > 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) + "]"); }}))); }Copy the code

This method is called in the main method of DispatcherHandler, where lookupRoute returns the Route from the predicate rule return R.predicate ().apply(exchange), The Route object is then retrieved via the GATEWAY_ROUTE_ATTR property in the subsequent FilteringWebHandler and all the Filters in the object are executed.

        @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);
      // TODO: needed or cached?
      AnnotationAwareOrderComparator.sort(combined);

      if (logger.isDebugEnabled()) {
         logger.debug("Sorted gatewayFilterFactories: " + combined);
      }

      return new DefaultGatewayFilterChain(combined).filter(exchange);
   }

Copy the code

The previous flow illustrates the basic workings of the entire Gateway, and through the previous logic, you can see that routing information is obtained primarily through the RouteLocator class’s getRoutes method:


    private AsyncPredicate<ServerWebExchange> lookup(RouteDefinition route, PredicateDefinition predicate) {
      RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName());
      if (factory == null) {
         throw new IllegalArgumentException("Unable to find RoutePredicateFactory with name " + predicate.getName());
      }
      if (logger.isDebugEnabled()) {
         logger.debug("RouteDefinition " + route.getId() + " applying " + predicate.getArgs() + " to "
               + predicate.getName());
      }

      // @formatter:off
      Object config = this.configurationService.with(factory)
            .name(predicate.getName())
            .properties(predicate.getArgs())
            .eventFunction((bound, properties) -> new PredicateArgsEvent(
                  RouteDefinitionRouteLocator.this, route.getId(), properties))
            .bind();
      // @formatter:on

      return factory.applyAsync(config);
   }
Copy the code

The core code:

      RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName());
Copy the code

RoutePredicateFactory = RoutePredicateFactory = RoutePredicateFactory;

default String name(a) {
      return NameUtils.normalizeRoutePredicateName(getClass());
   }
Copy the code

Found by looking at the implementation method

return removeGarbage(clazz.getSimpleName().replace(RoutePredicateFactory.class.getSimpleName(), ""));
Copy the code

That is to say, if their implementation RoutePredicateFactory ends in RoutePredicateFactory, can want to PathRoutePredicateFactory inside that’s assertion that the Path = / * * then automatically find assertion implementation class, Otherwise, you have to implement the getSimpleName method yourself.

A similar implementation is to load the GatewayFilterFactory through information in the routing Filters:


         GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());

Copy the code

GatewayFilterFactory:

default String name(a) {
      // TODO: deal with proxys
      return NameUtils.normalizeFilterFactoryName(getClass());
   }
Copy the code

At this point, the basic principle of the Gateway is basically complete, and there are other functions that are not related to the main process, but can be used as extensions to suit personalized needs.