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