Spring Cloud Gateway one-time request call source code analysis

Introduction: Recently, through in-depth study of Spring Cloud Gateway, we found that the architecture design of this framework is very simple and effective, and the design of many components is very worth learning. This paper makes a brief introduction to Spring Cloud Gateway and a detailed analysis of the processing flow of a request for Spring Cloud Gateway.

Author Xun Zheng
Source: Ali technical official account

I. Preface

Recently, through in-depth study of Spring Cloud Gateway, it is found that the architecture design of this framework is very simple and effective, and the design of many components is very worth learning. This paper makes a brief introduction to Spring Cloud Gateway and makes a more detailed analysis on the processing flow of a request for Spring Cloud Gateway.

II. Introduction

Spring Cloud Gateway is an API gateway officially launched by spring. The framework includes Spring5, SpringBoot2 and Project Reactor, in which the underlying communication framework uses netty. At the beginning of the launch of Spring Cloud Gateway, Netflix has launched an API gateway framework ZUUL with similar functions, but ZUUL has a disadvantage that the communication mode is blocked. Although it was later upgraded to non blocking ZUUL2, because Spring Cloud Gateway has been launched for some time, it also faces the problem of lack of data Factors with poor maintainability are not widely used.

1 key terms

When using Spring Cloud Gateway, you need to understand three modules, namely

Route:

That is, a set of routing rules. It is a metadata class that sets attributes such as URI, predicate and filter.

Predicate:

This is a method of Java 8 functional programming. It can be seen here that the route rule takes effect when the conditions are met.

Filter:

Filter can be regarded as the core module of Spring Cloud Gateway. Fuse, security, logic execution and network call are all completed by filter, which is subdivided into gateway filter and global filter. The difference is whether a specific route rule takes effect or all route rules take effect.

You can take a look at the previous code:

 @RequestMapping("/paramTest")
  public Object paramTest(@RequestParam Map<String,Object> param) {
      return param.get("name");
  }

  @Bean
  public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
      .route("path_route", r ->
                   r.path("/get")
                  .filters(f -> f.addRequestParameter("name", "value"))
                  .uri("forward:///paramTest"))
      .build();
  }
  • The route method represents a routing rule;
  • The path method represents a predicate. The reality behind it is the pathroutepredictefactory. The meaning of this code is that when the path contains / get, the current rule will take effect.
  • The filters method means to add a filter that adds request parameters to the current routing rule. Each request adds a name:value key value pair to the parameters;
  • The meaning of uri method is where the final route goes. The forward prefix here will route the request to the DispatcherHandler of spring mvc for local logical calls. In addition to forward, http and https prefixes can also be used for http calls, and lb prefixes can be used for rpc calls after configuring the registry.

The above figure is a working principle diagram given in the official document of Spring Cloud Gateway. After receiving the request, Spring Cloud Gateway matches the routing rules, and then hands it to the web handler for processing. The web handler will execute a series of filter logic.

III. process analysis

1 accept request

The underlying framework of Spring Cloud Gateway is netty. The key class to accept requests is the ReactorHttpHandlerAdapter. What it does is very simple, that is, it converts netty's requests and responses into http requests and responses, and gives them to an http handler to execute the following logic. The following figure shows the source code of this class, retaining only the core logic.

@Override
public Mono< Void> apply(HttpServerRequest request, HttpServerResponse response) {
    NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(response.alloc());
    ServerHttpRequest adaptedRequest;
    ServerHttpResponse adaptedResponse;
//Conversion request
    try {
        adaptedRequest = new ReactorServerHttpRequest(request, bufferFactory);
        adaptedResponse = new ReactorServerHttpResponse(response, bufferFactory);
    }
    catch (URISyntaxException ex) {
        if (logger.isWarnEnabled()) {
        ...
    }
    ...
    return this.httpHandler.handle(adaptedRequest, adaptedResponse)
            .doOnError(ex -> logger.warn("Handling completed with error: " + ex.getMessage()))
            .doOnSuccess(aVoid -> logger.debug("Handling completed with success"));
}

2 WEB filter chain

The first thing http handler does is to convert request and response into an exchange. This exchange is very core and is the carrier of parameter flow between filters. This class includes request, response and attributes (extended fields). The next thing it does is to execute the web filter chain, in which the logic is mainly monitoring.

Among them, WebfilterChainParoxy will lead to a new filter chain, mainly the logic related to security, logging and authentication. It can be seen that the filter design of Spring Cloud Gateway is nested layer by layer and has strong scalability.

3 find routing rules

The core class is route predicate handlermapping. The logic is also very simple, that is, traverse all the predictors of the route rules to see which predictor can hit. The core code is:

return this.routeLocator.getRoutes()
      .filter(route -> {
         ...
         return route.getPredicate().test(exchange);
      })

Because I use path for filtering here, the logic behind it is completed by PathRoutePredicateFactory. There are many predicate rules in addition to PathRoutePredicateFactory.

These routing rules can be found in official documents.

4 core filter chain execution

After finding the routing rule, the next step is to execute it. The core class here is FilteringWebHandler. The source code is:

The thing to do is simple:

  • Gets the filter at the route level
  • Get global filter
  • The two filters are put together and sorted according to order
  • Execute filter chain

Because my configuration contains a logic for adding request parameters, the red line arrow is the gateway filter I configured, named AddRequestParameterGatewayFilterFactory, and the rest are gloabl filters. The functions of these filters are mainly url parsing, request forwarding, response writeback and other logic, because we use forward schema here, Therefore, request forwarding will be performed by ForwardRoutingFilter.

5 request forwarding

ForwardRoutingFilter is also very simple. It directly reuses the ability of spring mvc and submits the request to dispatcherHandler for processing. dispatcherHandler will find the logic to be executed by the target processor according to the path prefix.

@Override
public Mono< Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
   URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);

   String scheme = requestUrl.getScheme();
   if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {
      return chain.filter(exchange);
   }
   setAlreadyRouted(exchange);

   //TODO: translate url?

   if (log.isTraceEnabled()) {
      log.trace("Forwarding to URI: "+requestUrl);
   }

   return this.dispatcherHandler.handle(exchange);
}

6 response writeback

The core class of response writeback is NettyWriteResponseFilter, but you can notice that the order of NettyWriteResponseFilter in the actuator chain is at the top. According to reason, the class of response processing should be later. The design here is more ingenious. You can see chain.filter(exchange).then(), which means to skip the next one when I execute it, and execute this logic only after the subsequent filters are executed. This behavior control method is worth learning.

@Override
public Mono< Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
   // NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_ATTR is not added
   // until the WebHandler is run
   return chain.filter(exchange).then(Mono.defer(() -> {
      HttpClientResponse clientResponse = exchange.getAttribute(CLIENT_RESPONSE_ATTR);

      if (clientResponse == null) {
         return Mono.empty();
      }
      log.trace("NettyWriteResponseFilter start");
      ServerHttpResponse response = exchange.getResponse();

      NettyDataBufferFactory factory = (NettyDataBufferFactory) response.bufferFactory();
      //TODO: what if it's not netty

      final Flux< NettyDataBuffer> body = clientResponse.receive()
            .retain() //TODO: needed?
            .map(factory::wrap);

      MediaType contentType = response.getHeaders().getContentType();
      return (isStreamingMediaType(contentType) ?
            response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body));
   }));
}

IV. summary

After reading the Spring Cloud Gateway request process code as a whole, I have several feelings:

  • Filter is the core design of Spring Cloud Gateway. It can even be exaggerated to say that Spring Cloud Gateway is a filter chain execution framework rather than an API gateway, because the actual request forwarding and request response writeback of API gateway are done in the filter, which are logic that Spring Cloud Gateway cannot perceive.
  • The module for obtaining Spring Cloud Gateway routing rules has room for optimization because it is obtained through circular traversal. If there are many route rules and the predicate rules are complex, you can consider using map for optimization. The route rules and predicate rules of the current day will not be very complex. Considering the readability of the code, there is no problem with the current method.
  • As an API gateway framework, there are many built-in filters. If there is a filter unloading function, it may be better. Users can unload unnecessary functions according to the actual situation, reducing the logic overhead behind it. In the API gateway scenario with a large number of calls, the benefits will be considerable.

Original link
This article is the original content of Alibaba cloud and cannot be reproduced without permission.

Keywords: Distribution Cloud Native

Added by steonkeys on Tue, 23 Nov 2021 00:51:46 +0200