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