I heard that wechat search “Java fish” will change strong oh!

This article is posted on Github and Gitee, and contains my entire Java series of articles for study and interview purposes

(a) What is microservice gateway

After the back end writes all the microservices, it is ultimately up to the front end to call them. As we all know, each microservice has its own port number. If the front end directly calls the microservice through IP and port, it will be very troublesome. It would also be difficult to impose restrictions on requests. This is where the microservices gateway comes in.

The microservice gateway becomes the API gateway, which is the only entry to the system. The APl gateway encapsulates the internal architecture of the system and provides a custom APl for each client. The core point of the API gateway approach is that all clients and consumers access microservices through a unified gateway and handle all non-business functions at the gateway layer. Typically, gateways also provide REST/HTTP access apis. The server registers and manages services through API-GW.

Sounds a little abstract, here is a diagram to show you: The front-end request is uniformly managed by the micro-service gateway, which can call each micro-service and also has a variety of functions.

The API gateway’s responsibilities include authentication, monitoring, load balancing, caching, request sharding and management, static response processing, and most importantly, external communication.

Common API Gateway implementations are Zuul, TraeFix, Spring Cloud Gateway, and so on. The current mainstream microservice Gateway is Spring Cloud Gateway.

(2) Spring Cloud Gateway

Spring Cloud Gateway is an official Gateway developed by Spring based on Spring 5.0, Spring Boot 2.0, Project Reactor and other technologies. It aims to provide a simple and effective unified API routing management method for microservices architecture. Unified access interface. As a Gateway in the Spring Cloud ecosystem, Spring Cloud Gateway aims to replace Netflix ZUUL. It not only provides a unified routing mode, but also provides basic Gateway functions based on Filter chain, such as security, monitoring/burying point, and flow limiting.

2.1 Core Concepts:

Route: A route is the most basic part of a gateway. The route information consists of an ID, a destination URL, a set of assertion factories, and a set of filters.

Predicates: assertion functions in java8. Assertion functions in the Spring Cloud Gateway allow developers to define and match any information from an Http Request. When the assertion is true, the route is matched.

Filter: Filters requests and responses

The following is to operate the microservice gateway through practice. This project relies on Nacos and Sentinel. If this part is not needed, it can be removed in the configuration file.

2.2 Building environment:

1. Introduce project import dependency

Create the api_gateway_server project and import the dependencies into pom. XML. The global dependencies of the project are as follows:

<? xml version="1.0" encoding="UTF-8"? > <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0. 0</modelVersion>
    <groupId>com.javayz</groupId>
    <artifactId>nacos-discovery-spring-cloud-gateway</artifactId>
    <version>0.01.-SNAPSHOT</version>
    <name>nacos-discovery-spring-cloud-gateway</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <projec.build.sourceEncoding>UTF-8</projec.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3. 0.RELEASE</spring-boot.version>
        <spring-cloud-alibaba.version>2.21..RELEASE</spring-cloud-alibaba.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <! --springcloudgateway--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> <version>1.72.</version> </dependency> </dependencies> <! <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
Copy the code

2. Configure the startup class

Create GatewayApplication class

@EnableDiscoveryClient
@SpringBootApplication
public class NacosDiscoverySpringCloudGatewayApplication {
    public static void main(String[] args) { SpringApplication.run(NacosDiscoverySpringCloudGatewayApplication.class, args); }}Copy the code

3. Write a configuration file

The configuration file is written in. Yml format. Create applicaiion.yml in the resource folder. Note the configuration of routes under gateway. The gateway id is used to define the gateway. Then define the URI, which means that when accessing port 9090 (i.e. gateway), the IP and port number are automatically changed to this URI. Predicates =/index/** Path=/index/** The address when the request for http://localhost:9090/index/1, the service gateway will visit http://nacos-discovery-consumer/index/1, that’s the function of micro service gateway to invoke the service.

server:
  port: 9090
spring:
  application:
    name: nacos-discovery-spring-cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true  Enable discoveryClient gateway integration to implement service discovery
      routes:
        - id: route1
          uri: lb://nacos-discovery-consumer  #lb:// indicates load balancing
          predicates:  # Judgment condition
            - Path=/test,/index/**
Copy the code

(3) Detailed route configuration

There are many matching rules in the route assertion, and there are many matching rules that I have just introduced Path

predicates:
    # match a time after which requests can be accessed (format ZonedDateTime)
    - After: = 2021-03-14 T11 31:08. 377 + 08:00 Asia/Shanghai 
    # match a time before which requests can be accessed (ZonedDateTime)
    - Before: = 2021-03-14 T11 31:08. 377 + 08:00 Asia/Shanghai
    # match two times before access
    - Between: = 2021-03-14 T11 31:08. 377 + 08:00 Asia/Shanghai, the 2021-03-14 T12:31:08. 377 + 08:00 Asia/Shanghai # Route between assertions
    # Route assertion Cookie match, match given name (such as master) or regular expression, as configured below, request Cookie must have token=master to access
    - Cookie=token,master
    The cookie is matched by the request header
    - Header=XXX,XXX
    # match by host address
    - Host=**.somehost.org   
    Match by request method
    - Method=GET  
    Match by request path
    - Path=***   
    If the token is configured, all requests must have token=XXX
   - Query=token
   Match by remote IP address
   - RemoteAddr = 192.168.1.1/24    
   The first parameter is the group name, the second parameter is the weight, the two routes are configured with different weights, the load will be based on the weight
   - Weight = group1, 8
Copy the code

When defining a request path, we can also pull the request path based on the microservice name. Here are two different ways to define a URI that accomplish the same function.

uri: http://127.0.0.1:9001 # When accessing port 9090, the IP address and port are automatically replaced with the uri
uri: lb:// userService # pull request path based on micro service name
Copy the code

Overall configuration:

server:
  port: 9090
spring:
  application:
    name: nacos-discovery-spring-cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true  Enable discoveryClient gateway integration to implement service discovery
      routes:
        - id: route1
          uri: lb://nacos-discovery-consumer  #lb:// indicates load balancing
          predicates:  # Judgment condition
            - Path=/test,/index/**,/**

    nacos:
      discovery:
        server-addr: 192.16878.128.: 8848
      username: nacos
      password: nacos

    sentinel:
      eager: true
      transport:
        dashboard: localhost:8080
Copy the code

3.1 Custom Assertion Configuration

The implementation of a custom assertion is two-step. Suppose we now want to implement a custom filter where the rule is that a token must be carried and the token must be equal to the specified value.

Define the configuration class: this contains the object values to be passed in the configuration file

@Data
public class TokenConfig {
    private String token;
}
Copy the code

2, define routing assertion factory: the first is named must be XXXRoutePredicateFactory AbstractRoutePredicateFactory inheritance

@Slf4j
@Component
public class TokenRoutePredicateFactory extends AbstractRoutePredicateFactory<TokenConfig> {
    public TokenRoutePredicateFactory(a){
        super(TokenConfig.class);
    }
    // To convert the values in the configuration into a collection
    @Override
    public List<String> shortcutFieldOrder(a) {
        return Collections.singletonList("token");
    }
    // Use jdk1.8's assertion interface to return true or false, if true, the filter does not intercept
    @Override
    public Predicate<ServerWebExchange> apply(TokenConfig config) {
        return exchange ->{
            // Get the request parameters
            MultiValueMap<String,String> valueMap=exchange.getRequest().getQueryParams();
            boolean flag=false;
            List<String> list=new ArrayList<>();
            // Save the value of the request to the collection
            valueMap.forEach((k,v)->{
                list.addAll(v);
            });
            // Check whether the value is the same as that in the configuration file
            for (String s:list){
                log.info("Token"+s);
                if (StringUtils.equalsIgnoreCase(s,config.getToken())){
                    flag=true;
                    break; }}returnflag; }; }}Copy the code

Finally, we add our own custom content to the assertion:

predicates:  # Judgment condition
  - Path=/test,/index/**,/**
  - Token=javayz
Copy the code

Only the link is http://localhost:9090/sentinel/test1? Token = JavayZ Only requests with token= Javayz can be accessed.

3.2 Assertion does not match 404 page custom

The default 404 page given by SpringCloudGateway is

This page is so unfriendly that we can customize it to return a JSON string

Create a new class called MyErrorWebExceptionHandler:

public class MyErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {

    public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    /** * specifies the response processing method as JSON *@param errorAttributes
     * @return* /
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(),this::renderErrorResponse);
    }


    /** * Get the corresponding HttpStatus * according to code@param errorAttributes
     * @return* /
    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        return (int) errorAttributes.get("status");
    }

    /** * Build exception information *@param request
     * @param ex
     * @return* /
    private String buildMessage(ServerRequest request,Throwable ex){
        StringBuilder builder = new StringBuilder("Failed to handle request:");
        builder.append(request.methodName());
        builder.append("");
        builder.append(request.uri());
        if(ex! =null){
            builder.append(ex.getMessage());
        }
        return builder.toString();
    }

    /** * Return json data *@param status
     * @param errorMsg
     * @param data
     * @return* /
    public static Map<String,Object> response(int status,String errorMsg,Object data){
        Map<String,Object> map=new HashMap<>();
        map.put("code",status);
        map.put("message",errorMsg);
        map.put("data",data);
        returnmap; }}Copy the code

Write another configuration class to inject custom exception page code into the Bean container:

@Configuration
public class GatewayConfiguration {
    private final ServerProperties serverProperties;
    private final ApplicationContext applicationContext;
    private final ResourceProperties resourceProperties;
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ServerProperties serverProperties, ApplicationContext applicationContext, ResourceProperties resourceProperties, ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean("myErrorWebExceptionHandler")
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler myErrorWebExceptionHandler(ErrorAttributes errorAttributes){
        MyErrorWebExceptionHandler exceptionHandler=new MyErrorWebExceptionHandler(
                errorAttributes,
                this.resourceProperties,
                this.serverProperties.getError(),
                this.applicationContext
        );
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        returnexceptionHandler; }}Copy the code

Finally, 404 is returned in JSON format for the caller to process.

(4) Spring Cloud Gateway filter

In addition to request routing, the Spring Cloud Gateway also supports request filtering.

Life cycle:

Filters have two life cycles, PRE and POST:

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.

Filter type:

GatewayFilter: Applies to a single route or a group of routes

GlobalFilter: Applies to all routes

Local filters can be used using the Spring Cloud Gateway built-in method in the official website (docs. Spring-clou…

Official filters:

Here are a few:

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: https://example.org
        filters:
        - AddRequestHeader=X-Request-red, blue
Copy the code

Adding the AddRequestHeader filter will add an X-Request-Red to the header field every time a Request is forwarded to the microservice.

- AddRequestParameter=red, blue
Copy the code

Add a Request parameter that is added each time a Request is forwarded to the microservice.

Customize global filters: Create a Filter package and create a GatewayFilter class under the package. To customize global filters, inherit the GlobalFilter, Ordered interface, and override the two methods of the interface

@Component
public class GatewayFilter implements GlobalFilter.Ordered {
    // Write the filter logic inside
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("Into the filter.");
        // Continue the execution
        return chain.filter(exchange);
    }
    // Filter priority. The smaller the value, the higher the priority
    @Override
    public int getOrder(a) {
        return 0; }}Copy the code

Through access, you can see the printed message appearing on the console.

Simulate user authentication process:

Global filters enable global user authentication. If the first attribute in the request is not access-Token or is empty, an httpStatus. UNAUTHORIZED message is returned

@Component
public class GatewayFilter implements GlobalFilter.Ordered {
    // Write the filter logic inside
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("Into the filter.");
        String token = exchange.getRequest().getQueryParams().getFirst("access-token");
        if (token==null) {// If no, the authentication fails
            System.out.println("No landing.");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }
    // Filter priority. The smaller the value, the higher the priority
    @Override
    public int getOrder(a) {
        return 0; }}Copy the code

Enter http://localhost:9090/sentinel/test1 in the browser, appear error, because there is no access token

Enter in your browser: http://localhost:9090/sentinel/test1? Access-token =1, get the correct result.

(5) Gateway traffic limiting

In the actual environment, there will be many requests to invoke the microservice at the same time. The influx of a large number of requests is likely to cause the microservice to be broken, so flow limiting becomes very important.

5.1 Common traffic limiting algorithms:

Counter algorithm: set the number of requests per unit of time. If the number of requests per unit of time is greater than this value, all subsequent requests will be rejected. Reception does not continue until the value per unit time falls below this value.

Leaky bucket algorithm: We can think of the leaky bucket algorithm as a funnel. Requests come in through the funnel and are then allocated to the microservice at a set output rate. When a lot of requests come in, you have to wait in the funnel. To control the flow, you need to set two variables: the size of the bucket and the rate at which the funnel flows out.

Token bucket algorithm: The token bucket algorithm is an improvement on the leaky bucket algorithm. Compared with the leaky bucket algorithm, the token bucket algorithm can allow a certain degree of burst call. The principle of the token bucket algorithm: in the token bucket algorithm, there is a bucket, used to store a fixed number of tokens, tokens will be placed in the bucket at a certain rate, once the bucket is full, no more tokens will be placed. When a request comes in, a token must be obtained in the token bucket before the microservice can be invoked. When there is no token in the token bucket, subsequent requests need to wait. In addition to limiting traffic, the token bucket algorithm also allows a degree of burst calls: for example, if a token bucket has a capacity of 100, the number of tokens in the bucket is full when no requests are made. If all of a sudden 100 requests come in, all 100 requests can be executed at once.

5.2 Integrate Sentinel for current limiting

Add the dependent

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
    <version>1.72.</version>
</dependency>
Copy the code

Add the Sentinel configuration:

spring:
  cloud:
    sentinel:
      eager: true
      transport:
        dashboard: localhost:8080
Copy the code

Inject the Sentinel filter into the Bean container

@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter(a){
    return new SentinelGatewayFilter();
}
Copy the code

This allows for Sentinel traffic limiting of gateways.

For the error message after traffic limiting, you can change the default error message mode to custom or jump to the error page:

@Configuration
public class GatewayConfiguration {
    private final ServerProperties serverProperties;
    private final ApplicationContext applicationContext;
    private final ResourceProperties resourceProperties;
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ServerProperties serverProperties, ApplicationContext applicationContext, ResourceProperties resourceProperties, ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    // Create a custom error handler
    @Bean(name = "myBlockRequestHandler")
    public BlockRequestHandler myBlockRequestHandler(a){
        BlockRequestHandler blockRequestHandler=new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                return ServerResponse.status(HttpStatus.BAD_GATEWAY)
                        .contentType(MediaType.APPLICATION_JSON)
                        .body(BodyInserters.fromValue("The service is restricted.")); }};return blockRequestHandler;
    }

    // Replace the default with a custom handler, or redirect
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(BlockRequestHandler myBlockRequestHandler){
        // Custom Block processing
        GatewayCallbackManager.setBlockHandler(myBlockRequestHandler);

        // Redirect block processing
        //GatewayCallbackManager.setBlockHandler(new RedirectBlockRequestHandler("http://www.baidu.com"));
        return new SentinelGatewayBlockExceptionHandler(viewResolvers,serverCodecConfigurer);
    }

    @Bean
    @Order(-1)
    public GlobalFilter sentinelGatewayFilter(a){
        return newSentinelGatewayFilter(); }}Copy the code

5.3 Cross-domain Gateway Implementation

This is similar to the cross-domain solution in SpringBoot: CorsConfig

@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter(a){
        CorsConfiguration configuration=new CorsConfiguration();
        configuration.addAllowedHeader("*");
        configuration.addAllowedMethod("*");
        configuration.addAllowedOrigin("*");

        UrlBasedCorsConfigurationSource source=new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/ *",configuration);
        return newCorsWebFilter(source); }}Copy the code

(6) Summary

This chapter contains a lot of content, but the basic content of SpringCloudGateway will be explained once again, I hope it will be helpful to you.