Hello, everyone. I’m Chen

Recently, there have been a lot of private messages from my friends urging me to update “Spring Cloud Advanced”. Chen also summed it up. The final reason is that Chen tried to explain an important knowledge point of a component thoroughly in one article, which led to a long article with a long writing time and tired friends.

Therefore, Chen decided to follow the article will be divided into different parts of each component, each article introduces one or two knowledge points, so as to improve the efficiency of the article, partners can also be more timely progress of learning.

This is the seventh article in Spring Cloud Advancements. The previous article is as follows:

  • How strong is Nacos, the soul ferryman of microservices, in 55 images?
  • OpenFeign kills series 9 asks, who can stand that?
  • Ali interview asked: Nacos, Apollo, Config configuration center how to select? These 10 dimensions tell you!
  • Ali failed in the interview: How to select 5 kinds of microservice registry? These dimensions tell you!
  • Ali’s Sentinel-killing Device 17 questions?
  • Compare 7 distributed transaction schemes, or prefer Ali open source Seata, really fragrant! (Principle + actual combat)

This article introduces an important role in micro-services: Gateway. How to choose Gateway? Because Alibaba has not made Gateway for the time being, of course, it chooses Spring Cloud Gateway.

The table of contents is as follows:

Why do you need a gateway?

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

Gatewayless microservice architectures often have the following problems:

  • The client requests different microservices multiple times, adding complexity to client code or configuration authoring.
  • Authentication is complex and each service requires independent authentication.
  • Cross-domain requests exist and are complicated to process in certain scenarios.

Basic functions of the gateway?

Gateway is the portal of all micro-services. Routing and forwarding are only the most basic functions. Besides, there are other functions, such as authentication, authentication, fusing, traffic limiting, log monitoring, and so on………

These application scenarios will be covered in more detail in a future article and are not the focus of today.

Why Spring Cloud Gateway?

Zuul gateway is used in 1.x version. However, in the 2.x release, Zuul’s upgrade was delayed, and Spring Cloud eventually developed a Gateway to replace Zuul, the Spring Cloud Gateway.

Spring Cloud Gateway is definitely the choice of Spring Cloud Gateway. Many of its ideas are borrowed from Zuul, and its functions and performance are definitely better than Zuul. Otherwise, why should Spring Cloud release it?

One important reason:

The Spring Cloud Gateway is built based on Spring Boot 2.x, Spring WebFlux, and Project Reactor.

Don’t worry about easy compatibility and performance for Spring Boot integration.

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

  1. Route: The basic building block of the Gateway. It consists of an ID, a target URI, an assertion set, and a filter set. If the aggregation assertion is true, the route is matched.
  2. Predicate: referring to Java8’s new feature, Predicate, which allows developers to match anything in an HTTP request, such as headers or parameters.
  3. Filters: You can modify the content of the request and response before or after the request is returned.

How to set up the gateway?

Why do we have this picture?

Be sure to adapt according to the version in the figure above, otherwise there will be unexpected bugs, Chen encountered, are tears…………

Create cloud-gateway9023 and add the following dependencies:

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

Note: The spring-boot-starter-web dependency must be removed; otherwise, an error will be reported during startup

Well, the project construction is complete, in fact, add such a dependency, about the detailed configuration below.

What is Predict?

Predicate comes from the Java8 interface. Predicate takes an input parameter and returns a Boolean result. This interface contains default methods for grouping Predicate into other complex logic (such as and, or, or not).

It can be used to verify interface request parameters and determine whether new and old data is changed and needs to be updated.

Spring Cloud Gateway built many Predict, the Predict source in org. Springframework. Cloud. Gateway. Handler. The predicate package, interested can read. Some of the built-in assertions are shown below:

Above 11 kinds of assertions Chen here will not introduce how to configure, the official document is very clear.

IO /spring-clou…

The last type of weight assertion is used as an example to describe how to configure it. The configuration is as follows:

spring:
  cloud:
    gateway:
      # # routing
      routes:
        ## id is unique
        - id: gateway-provider_1
          uri: http://localhost:9024
          # configure assertions
          predicates:
            The /gateway/provider/** request Path will be routed to the uri http://localhost:9024
            - Path=/gateway/provider/**
            The Weight Route Predicate Factory assigns traffic to the same group based on the Weight. This assigns 80% of the traffic
            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
          ## Route forward URI
          uri: http://localhost:9025
          # configure assertions
          predicates:
            The /gateway/provider/** request Path will be routed to the uri http://localhost:9024
            - Path=/gateway/provider/**
            The Weight Route Predicate Factory assigns traffic to the same group based on the Weight, which is 20%
            The first group1 is the group name, and the second parameter is the weight
            - Weight=group1, 2
Copy the code

The routing policy is configured under Routes. The components are as follows:

  • id: Indicates the unique ID of the route. The name is arbitrary
  • uri: INDICATES the URI of route forwarding
  • predicates: Assertion configuration. Multiple configurations can be configured

Assertions in the Spring Cloud Gateway are named in a canonical format: xxxRoutePredicateFactory.

Such as the Weight of assertion: WeightRoutePredicateFactory, then configuration directly in front of Weight.

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

However, since the weights are configured and the packets are the same, traffic is allocated according to the weight ratio.

What is a filter?

The concept of filters is familiar from Spring MVC, and the role and life cycle of the Gateway filter are similar.

Gateway lifecycle:

  • PRE: This filter is invoked before the request is routed. We can use this filter to authenticate, select requested microservices in the cluster, log debugging information, and so on.
  • POST: This filter is executed after routing to the microservice. Such filters can be used to add standard HTTP headers to responses, collect statistics and metrics, send responses from microservices to clients, and so on.

The Filter of Gateway can be divided into two types:

  • GatewayFilter: applies to a single route or a grouped route (to be configured in the configuration file).
  • GlobalFilter: Applies to all routes (no configuration is required and takes effect globally)

GatewayFilter (local filter)

There are many local filters built into the Spring Cloud Gateway, as shown below:

The local filter takes effect only when configured on the specified route. By default, the local filter does not take effect.

AddResponseHeaderGatewayFilterFactory this filter, for example, add the Header to the original response, the configuration is as follows:

spring:
  cloud:
    gateway:
      # # routing
      routes:
        ## id is unique
        - id: gateway-provider_1
          uri: http://localhost:9024
          # configure assertions
          predicates:
            The /gateway/provider/** request Path will be routed to the uri http://localhost:9024
            - Path=/gateway/provider/**
          ## Configure filters (partial)
          filters:
            - AddResponseHeader=X-Response-Foo, Bar
Copy the code

X-response-foo =Bar in the header, as shown below:

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

More filters can be found in the official documentation: docs. Spring-clou…

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

Scenario: Simulate an authorization verification process. If the request header or request parameter contains token, the request is allowed; otherwise, the request will be directly intercepted and 401 will be returned, with the code as follows:

/** * The name must be of the xxxGatewayFilterFactory form * TOdo: simulated authorization validation, the specific logic depends on the business consumable */
@Component
@Slf4j
public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizeGatewayFilterFactory.Config> {

    private static final String AUTHORIZE_TOKEN = "token";

    // constructor to load Config
    public AuthorizeGatewayFilterFactory(a) {
        //
        super(AuthorizeGatewayFilterFactory.Config.class);
        log.info("Loaded GatewayFilterFactory [Authorize]");
    }

    // Assign parameters from the configuration file to the configuration class
    @Override
    public List<String> shortcutFieldOrder(a) {
        //Config.enabled
        return Arrays.asList("enabled");
    }

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

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

            ServerHttpResponse response = exchange.getResponse();
            // If token is empty, return 401 directly, not authorized
            if (StringUtils.isEmpty(token)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                // The process is complete
                return response.setComplete();
            }
            /** * todo chain.filter(exchange) ** chain.filter(). Then (* chain filter........... * / *)
            // Authorization normal, proceed to the next filter chain call
            return chain.filter(exchange);
        };
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Config {
        // Controls whether authentication is enabled
        private booleanenabled; }}Copy the code

Local filters take effect only after being configured in routes. The configuration is as follows:

spring:
  cloud:
    gateway:
      # # routing
      routes:
        ## id is unique
        - id: gateway-provider_1
          uri: http://localhost:9024
          # configure assertions
          predicates:
            The /gateway/provider/** request Path will be routed to the uri http://localhost:9024
            - Path=/gateway/provider/**
          ## Configure filters (partial)
          filters:
            - AddResponseHeader=X-Response-Foo, Bar
            # # AuthorizeGatewayFilterFactory custom filters configuration, the value is true need to verify authorization, false don't need
            - Authorize=true
Copy the code

The direct access to: http://localhost:9023/gateway/provider/port, don’t carry token, return to the diagram below:

Request parameters with token: http://localhost:9023/gateway/provider/port? Token = abCDcdecd -ddcdeICd12;

The above AuthorizeGatewayFilterFactory only involves filter pre-processing, rear processing is in the chain. The filter (). Then () in the then done () method, See the TimeGatewayFilterFactory in the project source code, the code is no longer posted, as shown below:

GlobalFilter

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

GlobalFilter functions in the same way as GatewayFilter except that GlobalFilter is scoped to all route configurations rather than bound to a specific route configuration. Multiple GlobalFilters can specify the Order in which each GlobalFilter is executed using the @Order or getOrder() methods. The smaller the Order value, the higher the priority of GlobalFilter execution.

Note that since there are both pre and Post filters, a pre filter with a smaller order value should be at the top of the Pre filter chain, and a Post filter with a smaller order value 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, the actual work also needs to customize the filter, the following is to introduce how to customize.

Scenario: Simulate the Access Log function of Nginx to record information about 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) {
        // Filter preprocessing
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().pathWithinApplication().value();
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        return chain
                // Continue calling filter
                .filter(exchange)
                //filter postprocessing
                .then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            HttpStatus statusCode = response.getStatusCode();
            log.info("Request path :{}, remote IP address :{}, response code :{}", path, remoteAddress, statusCode); })); }}Copy the code

Well, global filters do not need to be configured on the route and can be injected into the IOC container to take effect globally.

A request is issued, and the console prints the following:

Request path :/gateway/provider/port, remote IP address :/0:0:0:0:0:0:0:1:64114, response code:200 OK
Copy the code

How do I integrate the registry?

There is no integrated registry in the demo above, and each route configuration specifies a fixed service URI, as shown below:

What’s the harm in that?

  • Once the IP address of the service is changed, the URI in the route configuration must be changed
  • Load balancing fails in the service cluster

At this point, an integrated registry is required so that the gateway can automatically fetch urIs (load balancing) from the registry.

The first article in Chen’s Spring Cloud Advance column: fifty-five charts to tell you how strong Nacos is the soul ferrier of micro services.

Add Nacos dependency to POM file as follows:

<! -- NACOS Registry -->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
Copy the code

Enable the registry function on the startup class as shown below:

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:
        The IP address in nacOS-server is the port number
        server-addr: 127.0. 01.: 8848
Copy the code

The only difference in route configuration is the uri of the route in the format lb://service-name, which is written in a fixed format:

  • lb: Fixed format, which refers to getting microservices by name from NACOS and following load balancing policies
  • service-name: The service name of the NACOS registry, which is not an IP address

The complete configuration demo for integrating the Nacos registry is as follows:

spring:
  application:
    Specify the service name, the name in nacOS
    name: cloud-gateway
  cloud:
    nacos:
      discovery:
        The IP address in nacOS-server is the port number
        server-addr: 127.0. 01.: 8848
    gateway:
      # # routing
      routes:
        ## id is unique
        - id: gateway-provider_1
        ## uses the LB form to get the URI from the registry load balancing
          uri: lb://gateway-provider
          # configure assertions
          predicates:
            The /gateway/provider/** request Path will be routed to the uri http://localhost:9024
            - Path=/gateway/provider/**
          ## Configure filters (partial)
          filters:
            - AddResponseHeader=X-Response-Foo, Bar
Copy the code

LoadBalancerClientFilter LoadBalancerClientFilter LoadBalancerClientFilter LoadBalancerClientFilter LoadBalancerClientFilter

How to implement dynamic routing?

In all of the above examples, the gateway configuration is written to the configuration file of the project. If the route changes, the project must be rewritten, which is costly to maintain.

In fact, you can store the gateway configuration in the configuration center so that it can be centrally managed by the configuration center. Once routes are changed, you only need to modify them in the configuration center. In this way, one modification can be made and more effective.

Here, of course, Nacos is used as the configuration center, adding the following dependencies:

<! -- NacOS configuration center dependencies -->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
Copy the code

Some related configurations to specify Nacos as the configuration center in the bootstrap.yml file:

spring:
  application:
    Specify the service name, the name in nacOS
    name: cloud-gateway
  cloud:
    nacos:
      As a demonstration, only suffixes are configured, other groups are configured, and namespaces are configured as needed
      config:
        server-addr: 127.0. 01.: 8848
        Specifies that the file suffix is not yaml
        file-extension: yaml
Copy the code

Create dataId in the public namespace of nacOS as the configuration of cloud-gateway.yaml (no environment specified) as follows:

The configuration is complete at this point. As for the effect oneself move small hand try……………

How do I customize global exception handling?

As you can see from the previous tests, once the routed microservice is offline or disconnected, the Spring Cloud Gateway directly returns an error page, as shown below:

Obviously this kind of exception message is unfriendly, and the returned exception message must be customized in the backend separation architecture.

Traditional Spring Boot services use @ControllerAdvice to wrap global exception handling, but the request does not arrive because the service is offline.

Therefore, a layer of global exception handling must also be customized in the gateway so that it can interact with the client more friendly.

Spring Cloud Gateway provides a variety of global processing methods, today Chen only introduced one of the methods, the implementation is relatively elegant.

Create a class directly GlobalErrorExceptionHandler, realize ErrorWebExceptionHandler, rewrite the handle method, the code is as follows:

/** * global exception handling for the gateway *@Order(1) : priority must be lower than the 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 result of the response, 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]); }})); }}Copy the code

Now that the global exception handling is customized, let’s test it and return JSON data as shown in the following figure:

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

conclusion

Spring Cloud Gateway is here to share today, mainly introduces the following points:

  • Why do you need a gateway? Basic gateway functions
  • How to build a microservice gateway from scratch
  • Predict is the concept of assertion
  • The concept of filters, the filters built into the Spring Cloud Gateway, and how to customize them
  • How do I integrate the Nacos registry and achieve load balancing
  • How to integrate Nacos to achieve dynamic routing, achieve a modification, more effective role
  • Global exception handling

You think that’s it for Spring Cloud Gateway? Impossible, the follow-up has more in-depth and actual combat introduction, the next article introduces…..

The project source code has been uploaded to Github, the public number [code ape technology column] reply key words: 9528 access!

One last word (attention, don’t fuck me for nothing)

Chen each original article is carefully output, especially “Spring Cloud advanced” column article, knowledge too much, want to speak fine, must spend a lot of time to prepare, from knowledge to source demo.

If this article is helpful to you, or inspired by it, please like it, read it, forward it, and favorites it. Your support is the biggest motivation for me to stick to it!