Recently, I was working on the refactoring of the old system, and I needed to introduce a gateway service into the new system after the refactoring was completed, as an adaptation and proxy for the interface between the new system and the old system. Previously, many gateway applications used the Spring-Cloud-Netfilx solution based on the Zuul1.x version, but given that Zuul1.x had stopped iterating, it used a more traditional blocking (B)IO + multi-threaded implementation, which actually did not perform well. Then the Spring team simply redeveloped a set of gateway components on their own, and this is the Spring-Cloud-Gateway to be investigated.

Introduction

Spring Cloud Gateway relies on Spring Boot 2.0, Spring WebFlux, and Project Reactor. Many familiar synchronous libraries (such as Spring-Data and Spring-Security) and synchronous programming patterns are not available in Spring Cloud Gateway, so it is best to read the documentation for the three frameworks mentioned above first.

Spring Cloud Gateway relies on the Netty based runtime environment provided by Spring Boot and Spring WebFlux, it is not built as a WAR package or run in a traditional Servlet container.

Terminology

  • Route: A route is the basic component of a gateway. It is defined by an ID, a target URI, a collection of predicates (Predicate) and a collection of filters. If the predicate aggregation is judged to be true, the route is matched.
  • Predicate: uses java.util.Predicate introduced in Java8 based on functional programming. when using the predicate (aggregation) judgment, the input parameters are of type ServerWebExchange, which allows the developer to match any parameter from an HTTP request, such as HTTP request headers, HTTP request parameters, etc.
  • Filter: The GatewayFilter instance created by the specified GatewayFilter factory is used to modify the request (parameters) or response (parameters) before or after sending the request downstream.

Actually, Filter also includes GlobalFilter, but it is not mentioned in the official documentation.

Working Principle

The client sends a request to Spring Cloud Gateway and if the Gateway Handler Mapping module processes the current request if it matches a target route configuration, the request is forwarded to the Gateway Web Handler module. When the Gateway Web Handler module sends the request, it passes the request through a chain of filters that match the request. The reason the filters are separated by dashed lines in the above diagram is that the filter processing logic can be executed before or after the proxy request is sent. All pre type filters are executed before the proxy request is created (and sent), and all post type filters are executed when the proxy request is created (and sent).

See the above figure, if the external request comes in and falls into the filter chain, then the left side of the dotted line is the pre type filter, and the request goes through the pre type filter first, and then is sent to the target proxied service. The target proxied service responds to the request, and the response goes through the filter chain again, that is, through the filter chain on the right side of the dotted line, and these filters are the post filters.

Note that if the corresponding routing port is not explicitly specified in the routing configuration, the following default port will be used.

  • HTTP protocol, use port 80.
  • HTTPS protocol, use port 443.

Introduction of dependencies

It is recommended to introduce Spring-Cloud-Gateway directly through the Train version (in fact, I have checked that the code name of the Train version is actually the naming of the London Underground station, like the current Spring Cloud latest version is Greenwich.SR1, Greenwich can be found in the map of London Underground station, corresponding to the SpringBoot version is 2.1.x) into Spring-Cloud-Gateway, as this will keep up with the latest stable version of Spring-Cloud, and because Spring-Cloud-Gateway is based on the Netty runtime environment to start, there is no need to introduce the Servlet container with the spring-boot-starter-web with a Servlet container.

The parent POM introduces the following configuration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.1.4.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
</dependencyManagement>

submodule or the module POM that needs to introduce Spring-Cloud-Gateway introduces the following configuration.

1
2
3
4
5
6
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    </dependencies>

Creating a starter class is sufficient.

1
2
3
4
5
6
@SpringBootApplication
public class RouteServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(RouteServerApplication.class, args);
    }
}

Gateway Configuration

The gateway configuration eventually needs to be translated into a collection of RouteDefinition, with the following interface for the definition of the configuration.

1
2
3
public interface RouteDefinitionLocator {
    Flux<RouteDefinition> getRouteDefinitions();
}

Configuration through YAML files or streaming programmatic configuration (in fact, there is also DiscoveryClient for configuration with Eureka in the documentation, which will not be studied here for now) is ultimately aimed at creating a collection of RouteDefinition.

Yaml configuration

The configuration implementation is PropertiesRouteDefinitionLocator, associated with the configuration class GatewayProperties.

1
2
3
4
5
6
7
8
spring:
  cloud:
    gateway:
      routes:
       - id: datetime_after_route    # <------ Here is the ID of the route configuration
        uri: http://www.throwable.club  # <------ Here is the URI (Host) for routing the final destination Server
        predicates:                     # <------ Predicate set configuration, multiple are logically connected with and
         - Path=/blog    # <------- Key(name)=Expression,键is the ID of the predicate rule factory, and the value is generally the regular representation of the matching rule

Programmatic streaming configuration

Programmatic and streaming configurations rely on the RouteLocatorBuilder and the goal is to construct a RouteLocator instance.

1
2
3
4
5
6
7
8
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route(r -> r.path("/blog")
                .uri("http://www.throwable.club")
            )
            .build();
}

Routing Predicate Factory

Spring Cloud Gateway uses Route as part of the HandlerMapping component infrastructure of Spring-WebFlux, which means that when HandlerMapping does the matching, it includes the configured routing rules in the matching mechanism. Spring Cloud Gateway itself contains a number of built-in routing predicate factories. Each of these predicates matches a different attribute of an HTTP request. Multiple routing predicate factories can be combined together using and logic.

The built-in routing predicate factories currently provided by Spring Cloud Gateway are as follows.

Specify date-time rule routing predicates

There are three optional rules for the routing predicate specified by the configured datetime.

  • Match requests before the specified datetime.
  • The match request is after the specified date time.
  • The matching request is between the specified datetime.

It is important to note that the configured datetime must satisfy the ZonedDateTime format.

1
2
//The year, month, day and hour, minute and second are separated by 'T', then -07:00 is the time difference from UTC, and the last [America/Denver] is the time zone.
2017-01-20T17:42:47.789-07:00[America/Denver]

For example, if the gateway application is live on 2019-05-01T00:00:00+08:00 [Asia/Shanghai], and all requests after the live date are routed to www.throwable.club, then the configuration is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server 
  port: 9090
spring:
  cloud:
    gateway:
      routes:
       - id: datetime_after_route
        uri: http://www.throwable.club
        predicates:
         - After=2019-05-01T00:00:00+08:00[Asia/Shanghai]

In this case, as long as the request gateway http://localhost:9090, the request will be forwarded to http://www.throwable.club.

If you want to allow only requests before 2019-05-01T00:00:00+08:00[Asia/Shanghai], then just change it to.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server 
  port: 9091
spring:
  cloud:
    gateway:
      routes:
       - id: datetime_before_route
        uri: http://www.throwable.club
        predicates:
         - Before=2019-05-01T00:00:00+08:00[Asia/Shanghai]

If only the time between two date periods is allowed to be requested, then simply read

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server 
  port: 9090
spring:
  cloud:
    gateway:
      routes:
       - id: datetime_between_route
        uri: http://www.throwable.club
        predicates:
         - Between=2019-05-01T00:00:00+08:00[Asia/Shanghai],2019-05-02T00:00:00+08:00[Asia/Shanghai]

Then only requests from May 1, 2019, 0:00 to May 2, 2019, 0:00 will be routed properly.

The CookieRoutePredicateFactory takes two parameters, the name of the Cookie and a regular expression (value). Only if the name and value corresponding to the Cookie in the request match the values configured in the Cookie route predicate will a hit be matched for routing.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server 
  port: 9090
spring:
  cloud:
    gateway:
      routes:
       - id: cookie_route
        uri: http://www.throwable.club
        predicates:
         - Cookie=doge,throwable

The request needs to carry a cookie, the name is doge and the value needs to match the regular expression throwable to route to http://www.throwable.club.

Here we try to build an order Order service locally, based on SpringBoot 2.1.4, started on port 9091.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// main
@RestController
@RequestMapping(path = "/order")
@SpringBootApplication
public class OrderServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }

    @GetMapping(value = "/cookie")
    public ResponseEntity<String> cookie(@CookieValue(name = "doge") String doge) {
        return ResponseEntity.ok(doge);
    }
}

The application.yaml configuration for the order service.

1
2
3
4
5
spring:
  application:
    name: order-service
server:
  port: 9091

Gateway routing configuration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
spring:
  application:
    name: route-server
  cloud:
    gateway:
      routes:
        - id: cookie_route
          uri: http://localhost:9091
          predicates:
            - Cookie=doge,throwable
1
2
3
4
curl http://localhost:9090/order/cookie --cookie "doge=throwable"

//response
throwable

Header routing predicate

The HeaderRoutePredicateFactory takes two parameters, the name of the Header and a regular expression (value). Only if the name and value corresponding to the Header in the request match the values configured in the Header route predicate, a hit will be matched for routing.

A new /header endpoint is added to the order service.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@RestController
@RequestMapping(path = "/order")
@SpringBootApplication
public class OrderServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }

    @GetMapping(value = "/header")
    public ResponseEntity<String> header(@RequestHeader(name = "accessToken") String accessToken) {
        return ResponseEntity.ok(accessToken);
    }
}

The routing configuration of the gateway is as follows.

1
2
3
4
5
6
7
8
spring:
  cloud:
    gateway:
      routes:
        - id: header_route
          uri: http://localhost:9091
          predicates:
            - Header=accessToken,Doge
1
2
3
4
curl -H "accessToken:Doge" http://localhost:9090/order/header

// response
Doge

Host routing predicates

The HostRoutePredicateFactory only needs to specify a list of host names, and each element in the list supports the Ant naming style, using . is used as a separator and multiple elements are distinguished from each other using ,. The Host routing predicate actually targets the Host attribute in the HTTP request header.

A new /header endpoint is added to the order service.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@RestController
@RequestMapping(path = "/order")
@SpringBootApplication
public class OrderServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }

    @GetMapping(value = "/host")
    public ResponseEntity<String> host(@RequestHeader(name = "Host") String host) {
        return ResponseEntity.ok(host);
    }
}

The routing configuration of the gateway is as follows.

1
2
3
4
5
6
7
8
spring:
  cloud:
    gateway:
      routes:
        - id: host_route
          uri: http://localhost:9091
          predicates:
            - Host=localhost:9090
1
2
3
4
curl http://localhost:9090/order/host

//response
localhost:9091  # <--------- Note here that when routing to the order service, the Host will be modified to localhost:9091

In fact, it is possible to customize more diverse Host matching patterns and even support URI template variables.

1
2
3
- Host=www.throwable.**,**.throwable.**

- Host={sub}.throwable.club

Request method routing predicates

The MethodRoutePredicateFactory takes only one parameter: the HTTP request method to be matched.

The routing configuration of the gateway is as follows.

1
2
3
4
5
6
7
8
spring:
  cloud:
    gateway:
      routes:
        - id: method_route
          uri: http://localhost:9091
          predicates:
            - Method=GET

Configured this way, all incoming requests to the GET method of the gateway will be routed to http://localhost:9091.

A new /get endpoint is added to the order service.

1
2
3
4
@GetMapping(value = "/get")
public ResponseEntity<String> get() {
    return ResponseEntity.ok("get");
}
1
2
3
4
curl http://localhost:9090/order/get

// response
get 

Request path routing predicates

The PathRoutePredicateFactory takes a list of PathMatcher pattern paths and an optional flag bit parameter matchOptionalTrailingSeparator. This is one of the most commonly used routing predicates.

1
2
3
4
5
6
7
8
spring:
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://localhost:9091
          predicates:
            - Path=/order/path
1
2
3
4
@GetMapping(value = "/path")
public ResponseEntity<String> path() {
    return ResponseEntity.ok("path");
}
1
2
3
4
curl http://localhost:9090/order/path

// response
path 

In addition, paths can be configured with {segment} placeholders such as /foo/1 or /foo/bar or /bar/baz, if configured in this form, when matching hits for routing, the corresponding content in the path will be extracted and the key-value pairs will be placed in the ServerWebExchange. getAttributes() collection, KEY is ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE, these extracted attributes can be used by GatewayFilter Factories.

Request query parameter routing predicate

The QueryRoutePredicateFactory takes a mandatory request query parameter (name of param) and an optional regular expression (regexp).

1
2
3
4
5
6
7
8
spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: http://localhost:9091
        predicates:
        - Query=doge,throwabl.

The param configured here is doge and the regular expression is throwabl..

1
2
3
4
@GetMapping(value = "/query")
public ResponseEntity<String> query(@RequestParam("name") String doge) {
  return ResponseEntity.ok(doge);
}
1
2
3
4
curl http://localhost:9090/order/query?doge=throwable

// response
throwable 

Remote IP address routing predicate

The RemoteAddrRoutePredicateFactory match rule takes a list of CIDR symbolic (IPv4 or IPv6) strings (minimum value is 1), e.g. 192.168.0.1/16 (where 192.168.0.1 is the remote IP address and 16 is the subnet mask).

1
2
3
4
5
6
7
8
spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: http://localhost:9091
        predicates:
        - RemoteAddr=127.0.0.1
1
2
3
4
@GetMapping(value = "/remote")
public ResponseEntity<String> remote() {
  return ResponseEntity.ok("remote");
}
1
2
3
4
curl http://localhost:9090/order/remote

// response
remote 

There are actually many extensions to the routing predicate of remote IP routing, so I won’t start here for now.

Multiple routing predicate combinations

Because the predicates attribute in the routing configuration is actually a list, multiple routing rules can be added directly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: http://localhost:9091
        predicates:
        - RemoteAddr=xxxx
        - Path=/yyyy
        - Query=zzzz,aaaa

These rules are logically combined using and, e.g. the example above is equivalent to

1
2
3
4
5
request = ...
if(request.getRemoteAddr == 'xxxx' && request.getPath match '/yyyy' && request.getQuery('zzzz') match 'aaaa') {
    return true;
}
return false;

GatewayFilter Factory

A routing filter GatewayFilter allows modifying the content of an incoming HTTP request or the content of a returned HTTP response. The scope of a routing filter is a specific routing configuration. Spring Cloud Gateway provides a rich set of built-in GatewayFilter factories that can be selected on demand.

Because there are so many GatewayFilter factory classes, I’ll give a simple example here.

If we want to attach special HTTP request headers to some requests, we can use AddRequestHeaderX-Request-Foo:Bar, application.yml as follows.

1
2
3
4
5
6
7
8
spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: https://example.org
        filters:
        - AddRequestHeader=X-Request-Foo,Bar

Then all HTTP requests from the gateway portal will have a special HTTP request header added: X-Request-Foo:Bar.

The current built-in implementation of the GatewayFilter factory is as follows.

ID class type Function
StripPrefix StripPrefixGatewayFilterFactory pre Remove the first part of the request URL path, e.g. the original request path is /order/query, after processing it is /query
SetStatus SetStatusGatewayFilterFactory post Setting the HTTP status code (parsed from org.springframework.http.HttpStatus)
SetResponseHeader SetResponseHeaderGatewayFilterFactory post Set (add) the response header of the request response
SetRequestHeader SetRequestHeaderGatewayFilterFactory pre Set (add) request header
SetPath SetPathGatewayFilterFactory pre Set (override) request path
SecureHeader SecureHeadersGatewayFilterFactory pre Set security-related request headers, see SecureHeadersProperties
SaveSession SaveSessionGatewayFilterFactory pre Save WebSession
RewriteResponseHeader RewriteResponseHeaderGatewayFilterFactory post Re-response header
RewritePath RewritePathGatewayFilterFactory pre Rewrite the request path
Retry RetryGatewayFilterFactory pre Retry requests based on conditions
RequestSize RequestSizeGatewayFilterFactory pre Limit the size of the request in byte, exceeding the set value returns 413 Payload Too Large
RequestRateLimiter RequestRateLimiterGatewayFilterFactory pre Restricted flow
RequestHeaderToRequestUri RequestHeaderToRequestUriGatewayFilterFactory pre Change the request URL by the value of the request header
RemoveResponseHeader RemoveResponseHeaderGatewayFilterFactory post Remove the configured response header
RemoveRequestHeader RemoveRequestHeaderGatewayFilterFactory pre Remove the configured request header
RedirectTo RedirectToGatewayFilterFactory pre Redirect, need to specify HTTP status code and redirect URL
PreserveHostHeader PreserveHostHeaderGatewayFilterFactory pre Set the attribute preserveHostHeader carried by the request to true
PrefixPath PrefixPathGatewayFilterFactory pre Request path add predecessor path
Hystrix HystrixGatewayFilterFactory pre Integrating Hystrix
FallbackHeaders FallbackHeadersGatewayFilterFactory pre The Hystrix implementation allows for exception details to be carried through the request header if the hit downgrade logic
AddResponseHeader AddResponseHeaderGatewayFilterFactory post Add response headers
AddRequestParameter AddRequestParameterGatewayFilterFactory pre Add request parameters, limited to the Query parameter of the URL only
AddRequestHeader AddRequestHeaderGatewayFilterFactory pre Add request header

When using the GatewayFilter factory, you need to know its ID and how to configure it, which can be seen in the public static internal class XXXXConfig of the corresponding factory class.

GlobalFilter Factory

The function of GlobalFilter is actually the same as GatewayFilter, except that the scope of GlobalFilter is all route configurations, not bound to the specified route configuration. Multiple GlobalFilters can specify the order of execution of each GlobalFilter via the @Order or getOrder() methods. The smaller the order value, the higher the priority of GlobalFilter execution.

Note that since there are two types of filters, pre and post, the pre type filter should be at the top of the pre filter chain if the order value is smaller, and the post type filter should be at the bottom of the pre filter chain if the order value is smaller. The schematic diagram is as follows.

For example, to implement the load balancing feature, application.yml is configured as follows.

1
2
3
4
5
6
7
8
spring:
  cloud:
    gateway:
      routes:
      - id: myRoute
        uri: lb://myservice   # <-------- lb special tag will use LoadBalancerClient to search for target services for load balancing
        predicates:
        - Path=/service/**

The built-in GlobalFilter currently provided by Spring Cloud Gateway is as follows.

class Function
ForwardRoutingFilter Redirection
LoadBalancerClientFilter Load Balancing
NettyRoutingFilter Netty’s HTTP client routing
NettyWriteResponseFilter Netty response for write operations
RouteToRequestUrlFilter Update URL based on routing configuration
WebsocketRoutingFilter Websocket request forwarding to downstream

Most of the built-in GlobalFilters are related to the properties of ServerWebExchangeUtils, so we won’t go into depth here.

Cross configuration

The gateway can control the global CORS behavior through configuration. The global CORS configuration corresponds to the class CorsConfiguration, which is a mapping of the URL schema. For example, the application.yaml file is as follows.

1
2
3
4
5
6
7
8
9
spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "https://docs.spring.io"
            allowedMethods:
            - GET

In the above example, for all requested paths, CORS requests will be allowed from docs.spring.io and are GET methods.

To introduce spring-boot-starter-actuator, you need to do the following configuration to enable the gateway monitoring endpoint.

1
2
management.endpoint.gateway.enabled=true 
management.endpoints.web.exposure.include=gateway

Current list of supported endpoints.

ID request path HTTP method Description
globalfilters /actuator/gateway/globalfilters GET Show the list of GlobalFilter in the routing configuration
routefilters /actuator/gateway/routefilters GET Show the list of GatewayFilters bound to the corresponding route configuration
refresh /actuator/gateway/refresh POST Emptying the routing configuration cache
routes /actuator/gateway/routes GET Display a list of defined routing configurations
routes/{id} /actuator/gateway/routes/{id} GET Display the routing configuration already defined for the corresponding ID
routes/{id} /actuator/gateway/routes/{id} POST Add a new routing configuration
routes/{id} /actuator/gateway/routes/{id} DELETE Delete the routing configuration for the specified ID

where /actuator/gateway/routes/{id} adds a new route configuration request parameter in the following format.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "id": "first_route",
  "predicates": [{
    "name": "Path",
    "args": {"doge":"/throwable"}
  }],
  "filters": [],
  "uri": "https://www.throwable.club",
  "order": 0
}

Summary

Although the author is a developer at the bottom, but a long time ago said to friends around.

Reactive programming combined with synchronous non-blocking IO or asynchronous non-blocking IO is the mainstream direction of the current network programming framework, it is best to keep pace with the mainstream to master the use of these frameworks, the best ability to become their contributors

The common reactive programming frameworks currently available are.

  • Reactor and RxJava2, where Reactor is more common in back-end JVM applications, and RxJava2 is more common in APP clients written for Android.
  • Reactor-Netty, this is based on Reactor and Netty package .
  • Spring-WebFlux and Spring-Cloud-Gateway, where Spring-Cloud-Gateway depends on Spring-WebFlux and Spring-WebFlux underlying depends on Reactor-Netty.

According to this chain, it is better to learn Reactor and Netty systematically.

Reference.

Appendix

The Spring-Cloud-Gateway was chosen not only to use the new technology, but more importantly for its impressive performance improvements. The results of the benchmarking project spring-cloud-gateway-bench are as follows.

Proxy Avg Latency Avg Requests/Sec
Spring Cloud Gateway 6.61ms 32213.38
Linkered 7.62ms 28050.76
Zuul(1.x) 12.56ms 20800.13
None(Direct call) 2.09ms 116841.15

Reference https://www.throwx.cn/2019/05/03/spring-cloud-gateway-guide/