Spring Cloud Gateway deadly serial 10 Q?

Posted by smartknightinc on Thu, 09 Dec 2021 13:55:54 +0100

This article introduces an important role in microservices: gateway. As for how to select a gateway, Alibaba has not yet selected a gateway. Of course, it has chosen Spring cloud Gateway. After all, it is my son.

The article contents are as follows:

Why do I need a gateway?

In the traditional monomer architecture, only one service is open to the client to call, but in the microservice architecture, a system is divided into multiple microservices. How can the client call these microservices? If there is no gateway, the calling address of each microservice can only be recorded locally.

The microservice architecture without gateway often has the following problems:

  • The client requests different microservices many times, which increases the complexity of client code or configuration writing.
  • Authentication is complex, and each service needs independent authentication.
  • There are cross domain requests, which are relatively complex to process in certain scenarios.

What are the basic functions of the gateway?

Gateway is the portal of all micro services. Routing and forwarding is only the most basic function. In addition, there are other functions, such as authentication, authentication, fusing, current limiting, log monitoring and so on

The above application scenarios will be described in detail in subsequent articles, not today's focus.

Why Spring cloud Gateway?

In 1 Zuul gateway is adopted in version x; But in 2 In version x, zuul's upgrade has been skipped. Spring Cloud finally developed a gateway to replace zuul, that is, Spring Cloud Gateway.

You must choose your own son Spring Cloud Gateway. Many of its ideas are based on zuul. The so-called green is better than blue, and its function and performance must be better than zuul. Otherwise, why should Spring Cloud release it?

One important reason:

Spring Cloud Gateway is based on spring boot 2 x. Spring WebFlux and [Project Reactor build.

There is no need to worry about the integration, compatibility and performance of Spring Boot.

How many terms do you need to know about Spring Cloud Gateway?

  1. Route: the basic building block of gateway. It consists of ID, destination URI, assertion set and filter set. If the aggregate assertion result is true, the route is matched.
  2. Predicate: referring to the new feature of Java 8, predicate allows developers to match any content in HTTP requests, such as headers or parameters.
  3. filter: you can modify the contents of the request and response before or after returning the request.

How to build the gateway?

Why put this picture?

Be sure to adapt according to the version in the figure above, otherwise unexpected bugs will appear. Chen has encountered them and they are all tears

Create a new cloud-gateway 9023 and add the following dependencies:

<!--gateway-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

Note: the spring boot starter web dependency must be removed, or an error will be reported during startup

Well, after the project is built, in fact, such a dependency is added. The detailed configuration is described below.

What is a Predict?

Predicate comes from the interface of java8. Predicate accepts an input parameter and returns a boolean result. The interface contains a variety of default methods to combine predicate into other complex logic (such as and, or, not).

It can be used to request parameter verification of the interface and judge whether new and old data have changed and need to be updated.

Spring Cloud Gateway has built-in predictions. The source code of these predictions is in
org.springframework.cloud.gateway.handler.predicate package. You can read it if you are interested. Some built-in assertions are as follows:

Chen will not introduce how to configure the above 11 assertions here. The official documents are very clear.

Official documents:
https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/

The following describes how to configure the last weight assertion as an example. The configuration is as follows:

spring:
  cloud:
    gateway:
      ## route
      routes:
        ## As long as the id is unique, the name is arbitrary
        - id: gateway-provider_1
          uri: http://localhost:9024
          ## Configuration assertion
          predicates:
            ## Path Route Predicate Factory asserts that all paths satisfying the request of / gateway/provider / * * will be routed to http://localhost:9024 In this uri
            - Path=/gateway/provider/**
            ## Weight route predict factory: the traffic of the same group is allocated according to the weight, and 80% is allocated here
            ## The first group1 is the group name, and the second parameter is the weight
            - Weight=group1, 8
            
        ## id must be unique
        - id: gateway-provider_2
          ## uri of route forwarding
          uri: http://localhost:9025
          ## Configuration assertion
          predicates:
            ## Path Route Predicate Factory asserts that all paths satisfying the request of / gateway/provider / * * will be routed to http://localhost:9024 In this uri
            - Path=/gateway/provider/**
            ## Weight route predict factory: the traffic of the same group is allocated according to the weight, and 20% is allocated here
            ## The first group1 is the group name, and the second parameter is the weight
            - Weight=group1, 2

Under routes is the configured routing policy. The components are as follows:

  • id: the unique id of the route. Any name
  • uri: uri of route forwarding
  • Predicates: assertion configuration. Multiple predicates can be configured

The naming of assertions in the Spring Cloud Gateway is standardized. The format is xxroutepredicatefactory.

For example, the assertion of weight:
WeightRoutePredicateFactory, the previous Weight is directly taken during configuration.

Default route forwarding: if two routes are routed, they are forwarded according to the configuration sequence. The above paths are configured: Path=/gateway/provider / * *. If no weight is configured, they must be forwarded to http://localhost:9024 .

However, since the weight is configured and the same grouping is configured, the traffic is allocated according to the weight proportion.

What is a filter?

The concept of filter is very familiar. I have been in contact with Spring mvc. The function and life cycle of Gateway filter are similar.

Gateway lifecycle:

  • PRE: this filter is called before the request is routed. We can use this filter to authenticate, select the requested micro service in the cluster, and record debugging information.
  • POST: this filter is executed after routing to the micro service. This filter can be used to add standard HTTP headers for responses, collect statistics and indicators, send responses from the micro service to clients, etc.

The Filter of Gateway can be divided into two types from the scope:

  • Gateway filter: applied to a single route or a packet route (it needs to be configured in the configuration file).
  • GlobalFilter: applied to all routes (no configuration, global effect)

GatewayFilter (local filter)

Many local filters are built in the Spring Cloud Gateway, as shown below:

The local filter can only take effect after the specified route configuration. It does not take effect by default.

with
AddResponseHeaderGatewayFilterFactory takes this filter as an example to add a Header for the original response. The configuration is as follows:

spring:
  cloud:
    gateway:
      ## route
      routes:
        ## As long as the id is unique, the name is arbitrary
        - id: gateway-provider_1
          uri: http://localhost:9024
          ## Configuration assertion
          predicates:
            ## Path Route Predicate Factory asserts that all paths satisfying the request of / gateway/provider / * * will be routed to http://localhost:9024 In this uri
            - Path=/gateway/provider/**
          ## Configure filter (local)
          filters:
            - AddResponseHeader=X-Response-Foo, Bar

When the browser requests, it is found that the key value pair X-Response-Foo=Bar already exists in the response header, as shown in the following figure:

Note: the name of the filter only needs to be prefixed. The name of the filter must be xxxGatewayFilterFactory (including customization).

See the official document for more filter configurations:
https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#gatewayfilter-factories

Although the built-in filter can solve many scenarios, it is inevitable that there are some special needs to customize a filter. Here is how to customize a local filter.

Scenario: simulate an authorization verification process. If a token is carried in the request header or request parameters, it will be released. Otherwise, it will be directly intercepted and returned to 401. The code is as follows:

/**
 * The name must be in the form of xxxGatewayFilterFactory
 * todo: Simulate the verification of authorization, and the specific logic is improved according to the business
 */
@Component
@Slf4j
public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizeGatewayFilterFactory.Config> {

    private static final String AUTHORIZE_TOKEN = "token";

    //Constructor, loading Config
    public AuthorizeGatewayFilterFactory() {
        //Fixed writing
        super(AuthorizeGatewayFilterFactory.Config.class);
        log.info("Loaded GatewayFilterFactory [Authorize]");
    }

    //Read the parameter assignment in the configuration file and assign it to the configuration class
    @Override
    public List<String> shortcutFieldOrder() {
        //Config.enabled
        return Arrays.asList("enabled");
    }

    @Override
    public GatewayFilter apply(AuthorizeGatewayFilterFactory.Config config) {
        return (exchange, chain) -> {
            //Determine whether authorization verification is enabled
            if (!config.isEnabled()) {
                return chain.filter(exchange);
            }

            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();
            //Get token from request header
            String token = headers.getFirst(AUTHORIZE_TOKEN);
            if (token == null) {
                //Get token from request header parameter
                token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
            }

            ServerHttpResponse response = exchange.getResponse();
            //If the token is empty, 401 will be returned directly and unauthorized
            if (StringUtils.isEmpty(token)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                //After the processing is completed, it will be intercepted directly and will not continue
                return response.setComplete();
            }
            /**
             * todo chain.filter(exchange) The previous is the preprocessing of the filter
             *
             * chain.filter().then(
             *  Post treatment of filter
             * )
             */
            //Authorization is normal. Continue to call the next filter chain
            return chain.filter(exchange);
        };
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Config {
        // Controls whether authentication is turned on
        private boolean enabled;
    }
}

The local filter needs to be configured in the route to take effect. The configuration is as follows:

spring:
  cloud:
    gateway:
      ## route
      routes:
        ## As long as the id is unique, the name is arbitrary
        - id: gateway-provider_1
          uri: http://localhost:9024
          ## Configuration assertion
          predicates:
            ## Path Route Predicate Factory asserts that all paths satisfying the request of / gateway/provider / * * will be routed to http://localhost:9024 In this uri
            - Path=/gateway/provider/**
          ## Configure filter (local)
          filters:
            - AddResponseHeader=X-Response-Foo, Bar
            ## AuthorizeGatewayFilterFactory user defined filter configuration. If the value is true, authorization needs to be verified, and if false, authorization is not required
            - Authorize=true

Direct access to:
http://localhost:9023/gateway/provider/port , no token is carried, and the return is as shown in the figure below:

Request parameter with token:
http://localhost:9023/gateway/provider/port?token=abcdcdecd-ddcdeicd12, returned successfully, as shown in the following figure:

Above
The AuthorizeGatewayFilterFactory only involves the pre-processing of the filter, and the post-processing is in the chain filter(). It is completed in the then() method in then(). For details, see the TimeGatewayFilterFactory in the project source code, and the code will not be posted, as shown in the following figure:

GlobalFilter (Global filter)

Global filters are applied to all routes without developer configuration. Spring Cloud Gateway also has some built-in global filters, as shown in the following figure:

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

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

Of course, in addition to the built-in global filter, you also need to customize the filter in actual work. Here's how to customize it.

Scenario: simulate the Access Log function of Nginx and record the relevant information of each request. The code is as follows:

/**
 * Implement GlobalFilter
 */
@Slf4j
@Component
@Order(value = Integer.MIN_VALUE)
public class AccessLogGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //Preprocessing of filter
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().pathWithinApplication().value();
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        return chain
                //Continue calling filter
                .filter(exchange)
                //Post processing of filter
                .then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            HttpStatus statusCode = response.getStatusCode();
            log.info("Request path:{},long-range IP address:{},Response code:{}", path, remoteAddress, statusCode);
        }));
    }
}

Well, the global filter does not need to be configured on the route. It can take effect globally by injecting it into the IOC container.

At this time, a request is issued, and the console prints the following information:

Request path:/gateway/provider/port,long-range IP address:/0:0:0:0:0:0:0:1:64114,Response code:200 OK

How to integrate a registry?

There is no integration registry in the above demo. Each route configuration specifies a fixed service uri, as shown in the following figure:

What are the disadvantages of doing so?

  • Once the IP address of the service is modified, the uri in the routing configuration must be modified
  • Load balancing cannot be achieved in a service cluster

At this point, an integrated registry is needed to enable the gateway to automatically obtain the uri (load balancing) from the registry.

Of course, the registration center here chooses Nacos. For unfamiliar partners, please see the first article in Chen's Spring Cloud advanced column: 55 pictures to tell you how strong Nacos is the soul ferry of micro services?

Nacos dependency is added in pom file as follows:

<!--nacos Registration Center-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

The registry function is enabled on the startup class, as shown in the following figure:

Specify the address of the nacos registry in the configuration file:

spring:
  application:
    ## Specify the service name, the name in nacos
    name: cloud-gateway
  cloud:
    nacos:
      discovery:
        # Service address of nacos, IP address in nacos server: port number
        server-addr: 127.0.0.1:8848

The only difference in the routing configuration is the uri of the route. The format is LB: / / service name. This is a fixed writing method:

  • lb: fixed format, which means to obtain microservices by name from nacos and follow the load balancing policy
  • Service Name: the service name of the nacos registry, which is not in the form of an IP address

The complete configuration demo of the integrated Nacos registry is as follows:

spring:
  application:
    ## Specify the service name, the name in nacos
    name: cloud-gateway
  cloud:
    nacos:
      discovery:
        # Service address of nacos, IP address in nacos server: port number
        server-addr: 127.0.0.1:8848
    gateway:
      ## route
      routes:
        ## As long as the id is unique, the name is arbitrary
        - id: gateway-provider_1
        ## The lb form is used to obtain the uri from the load balancing of the registry
          uri: lb://gateway-provider
          ## Configuration assertion
          predicates:
            ## Path Route Predicate Factory asserts that all paths satisfying the request of / gateway/provider / * * will be routed to http://localhost:9024 In this uri
            - Path=/gateway/provider/**
          ## Configure filter (local)
          filters:
            - AddResponseHeader=X-Response-Foo, Bar

Why can load balancing be enabled when lb is specified? As mentioned earlier, the global filter LoadBalancerClientFilter is responsible for routing addressing and load balancing. You can see the following source code:

How to implement dynamic routing?

In the above examples, a series of gateway configurations are written into the project configuration file. Once the route is changed, the project must be restarted, so the maintenance cost is very high.

In fact, we can store the gateway configuration in the configuration center, which is managed by the configuration center. Once the route changes, we only need to modify it in the configuration center. In this way, we can achieve the purpose of one modification and more effective.

Here, of course, Nacos should be used as the configuration center. Add dependencies as follows:

<!--    nacos Configuration center dependency-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

In bootstrap Nacos is specified in the YML file as some related configurations of the configuration center:

spring:
  application:
    ## Specify the service name, the name in nacos
    name: cloud-gateway
  cloud:
    nacos:
      ## todo is here as a demonstration. Only suffixes are configured. Other groups and namespaces are configured as needed
      config:
        server-addr: 127.0.0.1:8848
        ## The specified file suffix is not yaml
        file-extension: yaml

Create a cloud gateway with dataId in the public namespace in nacos Yaml configuration (no environment is specified). The configuration contents are as follows:

So far, the configuration has been completed. As for the effect, let's try it with our little hands

How to customize global exception handling?

Through the previous test, we can see a phenomenon: once the routed micro service goes offline or loses contact, the Spring Cloud Gateway directly returns an error page, as shown in the following figure:

Obviously, this exception information is unfriendly. The returned exception information must be customized in the front and back-end separation architecture.

The traditional Spring Boot service uses @ ControllerAdvice to wrap the global exception handling, but because the service goes offline, the request does not arrive.

Therefore, a layer of global exception handling must be customized in the gateway in order to interact more friendly with the client.

Spring Cloud Gateway provides a variety of global processing methods. Today, Chen will only introduce one of them, and the implementation is quite elegant.

Create a class directly
Globalerrorexception handler implements ErrorWebExceptionHandler and rewrites the handle method. The code is as follows:

/**
 * Global exception handling for gateway
 * @Order(-1): Priority must be lower than ResponseStatusExceptionHandler
 */
@Slf4j
@Order(-1)
@Component
@RequiredArgsConstructor
public class GlobalErrorExceptionHandler implements ErrorWebExceptionHandler {

	private final ObjectMapper objectMapper;

	@SuppressWarnings({"rawtypes", "unchecked", "NullableProblems"})
	@Override
	public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
		ServerHttpResponse response = exchange.getResponse();
		if (response.isCommitted()) {
			return Mono.error(ex);
		}

		// Return in JOSN format
		response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
		if (ex instanceof ResponseStatusException) {
			response.setStatusCode(((ResponseStatusException) ex).getStatus());
		}

		return response.writeWith(Mono.fromSupplier(() -> {
			DataBufferFactory bufferFactory = response.bufferFactory();
			try {
				//todo returns the response results, which can be customized according to business requirements
				CommonResponse resultMsg = new CommonResponse("500",ex.getMessage(),null);
				return bufferFactory.wrap(objectMapper.writeValueAsBytes(resultMsg));
			}
			catch (JsonProcessingException e) {
				log.error("Error writing response", ex);
				return bufferFactory.wrap(new byte[0]);
			}
		}));
	}
}

OK, the global exception handling has been customized. After testing, JSON data is returned normally, as shown in the following figure:

The style of JSON is customized according to the needs of the architecture.

summary

The Spring Cloud Gateway will share here today, mainly introducing the following knowledge points:

  • Why do I need a gateway? Basic functions of gateway
  • How to build a microservice gateway from zero
  • Concept of Predict
  • The concept of filter, the built-in filter of Spring Cloud Gateway and how to customize it
  • How to integrate the Nacos registry and achieve load balancing
  • How to integrate Nacos to realize dynamic routing and achieve the effect of one modification and more effectiveness
  • Handling of global exceptions

Topics: Java Back-end Programmer architecture