1. Introduction
Spring-cloud-gateway as an excellent API gateway, how does it run and how does it integrate with other frameworks?
For details on how to use spring-cloud-gateway, please refer to the first two articles: How to use spring-cloud-gateway
Preparation:
Spring-cloud-gateway version: spring-cloud-gateway-2.1.0.release
Example project address: Github link
Note: to understand the source code, i.e. to have their own understanding of the framework, be sure to debug!! , the original is not easy, reprint please mark the source!!
2. Prepare
Sample requirements:
Implement a gateway function: 1) In addition to configuring the whitelist URI, other interfaces import authentication services (users must log in).
Login decision: The header contains a user-token and is valid.
2.1 environment
Eurake: Rapid deployment project – Github
Service provider: Rapid deployment project – Github
Gateway Gateway project: Rapid deployment project – Github
2.2 configuration
Certified filter factory 🏭
@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {
private Logger logger = LoggerFactory.getLogger(AuthGatewayFilterFactory.class);
/** * User login status token */
private static final String USER_TOKEN = "user_token";
public AuthGatewayFilterFactory(a){
super(Config.class);
logger.info("AuthGatewayFilterFactory init");
}
@Override
public List<String> shortcutFieldOrder(a) {
return Collections.singletonList("ignoreUrlListStr");
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// Verify whether the login URL is not required
String path = request.getPath().toString();
logger.info("AuthGatewayFilterFactory.apply path:{}",path);
String ignoreUrlListStr = config.ignoreUrlListStr;
logger.info("AuthGatewayFilterFactory.apply ignoreUrlListStr={}",ignoreUrlListStr);
boolean ignoreOk = Arrays.asList(ignoreUrlListStr.split("\ |")).contains(path);
if(ignoreOk){
return chain.filter(exchange);
}
// Verify whether to log in
HttpHeaders headers = request.getHeaders();
String userToken = headers.getFirst(USER_TOKEN);
if(StringUtils.isEmpty(userToken)){
// No login message is displayed
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
Map<String, Object> bodyMap = new HashMap<>();
bodyMap.put("code", -1000003);
bodyMap.put("message"."Not logged in");
byte[] responseByteArray = JSON.toJSONBytes(bodyMap);
DataBuffer responseBuffer = response.bufferFactory().allocateBuffer(responseByteArray.length).write(responseByteArray);
return response.writeWith(Mono.just(responseBuffer));
}
logger.info("AuthGatewayFilterFactory.apply user-token={}",userToken);
return chain.filter(exchange);
};
}
public static class Config {
private String ignoreUrlListStr;
public String getIgnoreUrlListStr(a) {
return ignoreUrlListStr;
}
public void setIgnoreUrlListStr(String ignoreUrlListStr) {
this.ignoreUrlListStr = ignoreUrlListStr; }}}Copy the code
application.properties
# Gateway configuration -- Login - free authentication configuration
spring.cloud.gateway.routes[1].id = login_auth
spring.cloud.gateway.routes[1].uri = lb://yiyi-example-api
#spring.cloud.gateway.routes[1].uri = localhost:18083
spring.cloud.gateway.routes[1].predicates[0] = Path=/eat/**
spring.cloud.gateway.routes[1].filters[0] = Auth=/login|/send_code
Copy the code
3. 💣 line
Note: Spring – the cloud – gateway rely on the web – flux framework, we can beat a breakpoint at first when the debug DispatcherHandler# hanlde (), customised AuthGatewayFilterFactory. The apply ();
3.1 Basic Review
Several important attributes in spring-cloud-gateway are as follows:
🆔 : Determine the uniqueness (based on service configuration).
Uri: indicates the URI after the jump.
Predicate; predicate; predicate Judge whether the jump condition is met; [Can be multiple]
Filter: intercepts the request and processes it before it is requested (for example, adding headers)
3.2 Target Positioning
Before we debug, what is our target?
1) Locate the loading position of route;
2) Locate the matching position of route;
3) Locate the beginning position of the filter execution;
4) Locate load balancing lb://yiyi-example-api, service domain name replacement position;
3.2 cook your food
Curl –location –request GET ‘localhost/eat/apple’
The following sequence diagram is obtained after debugging:
As shown above:
Spring – the cloud – gateway through RoutePredicateHandlerMapping complete access to the web – flux;
GetHandlerInternal (ServerWebExchange Exchange) does the lookup of the request handler;
Take a look at the official description of this method
// Finds the handler for the given request, and returns empty Mono if the specific request is not found. This method is called by getHandler.
protected abstractMono<? > getHandlerInternal(ServerWebExchange exchange);Copy the code
RoutePredicateHandlerMapping# getHandlerInternal annotated source
@Override
protectedMono<? > getHandlerInternal(ServerWebExchange exchange) {// don't handle requests on the management port if set
if(managmentPort ! =null && exchange.getRequest().getURI().getPort() == managmentPort) {
return Mono.empty();
}
exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());
/** * Add attributes, key: GATEWAY_ROUTE_ATTR * to {@linkFilteringWebHandler# handle (org. Springframework. Web. Server. ServerWebExchange)} call processing * /
returnlookupRoute(exchange) .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
FilteringWebHandler#handle annotated source code
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
/** * Add attributes, key: GATEWAY_ROUTE_ATTR * to {@linkRoutePredicateHandlerMapping# getHandlerInternal (org. Springframework. Web. Server. ServerWebExchange)} for storage * /
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
List<GatewayFilter> gatewayFilters = route.getFilters();
// Obtain gatewayFiler data including route.filter and globalFilter
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
RouteToRequestUrlFilter#filter annotated source code
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/ / get the Route
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
if (route == null) {
return chain.filter(exchange);
}
log.trace("RouteToRequestUrlFilter start");
URI uri = exchange.getRequest().getURI();
boolean encoded = containsEncodedParts(uri);
URI routeUri = route.getUri();
if (hasAnotherScheme(routeUri)) {
// this is a special url, save scheme to special attribute
// replace routeUri with schemeSpecificPart
exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, routeUri.getScheme());
routeUri = URI.create(routeUri.getSchemeSpecificPart());
}
if("lb".equalsIgnoreCase(routeUri.getScheme()) && routeUri.getHost() == null) {
//Load balanced URIs should always have a host. If the host is null it is most
//likely because the host name was invalid (for example included an underscore)
throw new IllegalStateException("Invalid host: " + routeUri.toString());
}
/ / stitching url
URI mergedUrl = UriComponentsBuilder.fromUri(uri)
// .uri(routeUri)
.scheme(routeUri.getScheme())
.host(routeUri.getHost())
.port(routeUri.getPort())
.build(encoded)
.toUri();
In {/ * * *@linkLoadBalancerClientFilter#filter(org.springframework.web.server.ServerWebExchange, Org. Springframework. Cloud. Gateway. Filter. Use * / GatewayFilterChain)}
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);
return chain.filter(exchange);
}
Copy the code
LoadBalancerClientFilter#filter annotated source code
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/ / URL
In {/ * * *@linkRouteToRequestUrlFilter#filter(org.springframework.web.server.ServerWebExchange, Org. Springframework. Cloud. Gateway. Filter. In this value * / GatewayFilterChain)}
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
if (url == null| | (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
// Add the original request URI to GATEWAY_ORIGINAL_REQUEST_URL_ATTR
//preserve the original url
addOriginalRequestUrl(exchange, url);
log.trace("LoadBalancerClientFilter url before: " + url);
// get a ServiceInstance for load balancing
final ServiceInstance instance = choose(exchange);
if (instance == null) {
String msg = "Unable to find instance for " + url.getHost();
if(properties.isUse404()) {
throw new FourOFourNotFoundException(msg);
}
throw new NotFoundException(msg);
}
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 requestUrl = loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
// Set the requestUrl to GATEWAY_REQUEST_URL_ATTR. The subsequent Routing-related GatewayFilter uses this attribute to initiate a request.
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
// Submit the filter chain to continue filtering
return chain.filter(exchange);
}
Copy the code
4. A summary
Refer to the sequence diagram in 3.2 to obtain the following request flow chart
Therefore, we have a basic grasp of the overall operation process and framework interaction logic of Spring-Cloud-Gateway, and can adapt to actual needs when using it
see
Spring – Cloud konjac source code analysis
Kotlin: Language version 1.1 is no longer supported; Both please, use version 1.2
Spring-cloud-gateway annotated source code – Github address