Basic use of GateWay in SpringCloud

Gateway introduction:

Gateway is the most marginal service of microservices (gateway is also a service), which is directly exposed to users and used as a bridge between users and microservices.

As the unified entrance of the microservice architecture system, the gateway provides a simple, effective and unified API route management for the microservice architecture system (providing route transfer of internal services), and can also implement some common logic that is not coupled with the business, such as token interception, permission verification, flow restriction, monitoring log and other operations.

Differences between gateway and service invocation:

When using the gateway: the request from the browser will arrive at the gateway first, and then the gateway will distribute the request to the corresponding service cluster in the registry, and perform load balancing

When the gateway is not used, the browser needs to specify the ip and port number of a service in the arriving service cluster to realize the call;

Summary: using the gateway simplifies the distribution of requests sent by the browser, directly exposes the gateway to customers, and acts as a bridge between customers and microservices

The gateway is not a service call, but a service call refers to a consumer service calling a provider service, which is the work of ribbon and openFeign; Therefore, the gateway is not a consumer service, because both the consumer service and the provider service do request processing in service invocation (there is a controller)

The gateway only serves as the route relay of the service, transferring the request to the service, and does not handle the request. - The premise of service invocation and service routing is service discovery first.

‚Äč

Difference between Nginx and Gateway

Gateway is an intermediate layer between nignx and application services, mainly responsible for routing requests to different microservices and verifying the legitimacy of requests.

Gateway is an internal gateway (route) from the front-end service to the back-end service, and nginx is an external gateway (route) from the user to the front-end service.

Workflow:

The client sends a request to the SpringCloud Gateway. The SpringCloud Gateway finds the route matching the request through the Gateway Handler Mapping, and then sends the request to the Gateway Web Handler. The Gateway Web Handler passes through the specified filter, then sends the request to the business logic of the actual service, and finally returns.

Summary: the core logic of Gateway is route forwarding + execution filter chain

Core concepts:

  1. Route
    A route consists of an id, a destination url, a set of assertions, and a set of filters; If the route assertion is true, the request url matches the configured route.

  2. Predicate

    Assertions are Boolean expressions that return true if the conditions are met and false if the conditions are not met.

    The assertion of SpringCloud Gateway allows developers to define and judge all the information from the Http request, such as the request path and request parameters, so as to match the route (judge whether the request path and request parameters in the Http request meet certain conditions, so as to match the corresponding route).

  3. Filter

    The filters in the gateway have the same functions as those in the Servlet. They both intercept requests and process them before and after the request.

use:

Step 1: create a project

<!--Because the gateway is also a service, the gateway project needs to be imported EurekaClient Dependence on-->
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
<!--Introduced gateway Dependence on-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

Step 2: Main startup class

//The gateway service also needs to be injected into the registry, so the startup class of the gateway project also needs to be annotated
@EnableEurekaClient

Step 3: application.properties file / java code = = = > configure Gateway

#Set the Gateway service name -- gateway-80
spring.application.name=gateway-80

#Set the Gateway port -- 80
server.port=80

#Register the Gateway service to the registry -- the value is the url address of the eureka server (Registry)
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

#---------------------------Gateway specifies the configuration of the route--------------------------
#Turn on the gateway (it is turned on by default)
spring.cloud.gateway.enabled=true

#Configure routes (the attribute value of spring.cloud.gateway.routes is List):
#Set the route ID (any, unique)
spring.cloud.gateway.routes[0].id=user-service-router

#Set the uri(ip:port) of the service to be routed
#When the http protocol is used for configuration, it is applicable to single service configuration, not cluster configuration
spring.cloud.gateway.routes[0].uri=http://localhost:8081
#When lb protocol is used for configuration, it is applicable to cluster configuration. At the same time, load balancing calls are made to the cluster, and polling algorithm is used;
#For the modification of the load balancing algorithm, refer to the method of modifying the load balancing algorithm in the Ribbon
spring.cloud.gateway.routes[0].uri=lb://service name

#Set the route assertion (matching rule of the request path in the service to which the route is routed)
spring.cloud.gateway.routes[0].predicates[0]=Path=/route

#---------------------------Configuration of Gateway dynamic route--------------------------
#Enable dynamic routing
spring.cloud.gateway.discovery.locator.enabled=true
#Allow service name in lower case
spring.cloud.gateway.discovery.locator.lower-case-service-id=true


//Create configuration class
//This is also the way to write the specified service
//Add configuration class annotation
//Using java code configuration, you can make some logical judgments in the code, such as security verification
@Configuration
public class GatewayConfig {

    //Configure the Bean object of RouteLocator to the IOC container
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder){
        
		//Create RouteLocatorBuilder.Builder object
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        
		//Call the route() method of the RouteLocatorBuilder.Builder object to configure the route
        //Connection loss mode
        routes
            .route("user-service-router",//Route id
                r -> r.path("/info/**")//Route assertion (matching rule of request path in the service to which the route is routed)
            .uri("http://localhost:8081 ") / / uri(ip:port) of the service to be routed
        )
            .build();

        //Call the build() method of the RouteLocatorBuilder.Builder object to create a RouteLocator object and return
        return routes.build();
    }
}

Supplement:

#Browser access path in single configuration:
localhost:port/route
#Access path of browser during cluster configuration
localhost/route	or	localhost/service name/route
#The single configuration and cluster configuration of gateway in application.properties can be used together

Overview of assertions

Assertions are Boolean expressions that return true if the conditions are met and false if the conditions are not met.

The assertion of SpringCloud Gateway allows developers to define and judge all the information from the Http request to match the route (judge whether the information in the Http request meets certain conditions to match the corresponding route).

SpringCloud Gateway has built-in many route assertion factories. Different assertion factories assert different information of HTTP requests, and multiple route assertions can be combined.

Several ways:

spring.cloud.gateway.routes[0].predicates[0]=Path=/info/**
#If the request path is / info / * * in the service, the route is available

spring.cloud.gateway.routes[0].predicates[1]=After=2020-06-20T17:42:47.789-07:00[Asia/Shanghai]
#After this point in time, the route is available

spring.cloud.gateway.routes[0].predicates[2]=Before=2020-06-18T21:26:26.711+08:00[Asia/Shanghai]
#Routes are available before this point in time

spring.cloud.gateway.routes[0].predicates[3]=Between=2020-06-18T21:26:26.711+08:00[Asia/Shanghai],2020-06-18T21:32:26.711+08:00[Asia/Shanghai]
#Route available between these two times

spring.cloud.gateway.routes[0].predicates[4]=Cookie=name,xiaobai
#If the request information contains a Cookie with a name and a value of xiaobai, the route is available

spring.cloud.gateway.routes[0].predicates[5]=Header=token,123456
#If the request information contains a request header with the name of token and the value of 123456, the route is available

spring.cloud.gateway.routes[0].predicates[6]=Host=www.hello.com
#If the host name is www.hello.com, the route is available

spring.cloud.gateway.routes[0].predicates[7]=Method=GET,POST
#If the request method is get or post, the route is available

spring.cloud.gateway.routes[0].predicates[8]=Query=username
#With the request parameter username, the route is available

Filter / processor:

Functions:

It intercepts requests and processes them before and after they are processed.,

Classification:

  1. GatewayFilter: local filter. It needs to be configured to a route to filter.
  2. GlobalFilter: Global filter. It does not need to configure routes. It will work on all routes during system initialization.

Configuration of global filter:

  1. Step 1: create a filter class, implement the GlobalFilter interface and Ordered interface, and inject it into the ioc container

    @Component
    public class GateWayFilter implements GlobalFilter, Ordered {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            return null;
        }
    
        @Override
        public int getOrder() {
            return 0;
        }
    }
    
  2. Rewrite both methods

        /**
         * Global filter, all url requests will arrive here before accessing the service
         */
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
            //Get the request object of this visit
            ServerHttpRequest request = exchange.getRequest();
            //Get the url of this visit through the request object
            String path = request.getPath().value();
            //Judge whether the current path contains the login path. Because the path contains the service name or other information, use include instead of equals to judge
            if (path.contains("/login")) {
                //Release the login request
                return chain.filter(exchange);
            }
    
            //Obtain the token in the request header; How to carry the token needs to be confirmed with the front end
            String token = request.getHeaders().getFirst("Authorization");
            //Determine whether the token is an empty string or null
            if (!StringUtils.isEmpty(token)) {
                //Because it contains a token, it is considered that it has been logged in
                //Omit redis query token
                //Release
                return chain.filter(exchange);
            }
            
            //In order to more intuitively observe the data returned to the web page after the authentication failure, I commented out what I wrote myself, and used someone else's
    //        //Access without token is denied
    //        //Get response object
    //        ServerHttpResponse response = exchange.getResponse();
    //        //Authentication failed
    //        response.setStatusCode(HttpStatus.UNAUTHORIZED);
    //        //Respond and return to the browser
    //        return response.setComplete();
    
             /*
              Not certified:
             */
            //Get response object
            ServerHttpResponse response = exchange.getResponse();
            //Add a response header and set the response type to application/json
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            //Response data, a map collection, is converted into json and then sent to the client
            Map<String, Object> data = new HashMap<>();
            data.put("code", 401);
            data.put("msg", "un_authorization");
            //Use ObjectMapper to convert
            ObjectMapper objMapper = new ObjectMapper();
            byte[] bytes = new byte[0];
            try {
                //Convert map into byte data of json
                bytes = objMapper.writeValueAsBytes(data);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            //Assemble the byte data of json into the data buffer
            DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
            //Respond to the client
            return response.writeWith(Mono.just(dataBuffer));
        }
    
    	//The function of this method is to determine the execution opportunity of this filter class in the filter chain according to the return value. 0 is the maximum. Cannot return a negative number
    	@Override
        public int getOrder() {
            return 0;
        }
    

Custom local filter, post processor:

  1. Step 1: create a Configuration class and mark the @ Configuration annotation

  2. Create method with return value of RouteLocator object

    package com.xa.config;
    
    import org.springframework.cloud.gateway.route.RouteLocator;
    import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import reactor.core.publisher.Mono;
    
    @Configuration
    public class FilterConfig {
        
        //Custom local filter
        @Bean
        public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder) {
            //Get route builder
            RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
            /*
            (String id, Function<PredicateSpec, AsyncBuilder> fn)
            Three parameters:
                The first parameter: id of the filter, which can be unique
                The second parameter: a Function interface, which needs to implement R apply(T t); Method uses lambda to simplify the implementation
             */
    
            return routes.route("login-filter-router", t -> {
                return t
                        //Set the url path of monitoring
                        .path("/login","/hello")
                        /*
                        Set the filtering of monitoring,
                        However, the parameters of this method are also an interface, and the R apply(T t) method needs to be implemented; Use lambda to simplify implementation
                         */
                        .filters(r -> {
                            /*
                            Set the filter;
                            modifyResponseBody(Class<T> inClass, Class<R> outClass, RewriteFunction<T, R> rewriteFunction)
                                First parameter: return value type of method
                                The second parameter: the data type returned to the web page
                                The third parameter: it is still an interface, and lambda is used to simplify the implementation
                                    1.Switch
                                    2.Return value after accessing the login method
                             */
                            return r.modifyResponseBody(String.class, String.class, (exchange, str) -> {
                                //The area for processing the return value of the login method
                                str += "back filter ";
                                //Use Mono.just to return the object required by the previous interface
                                //The data returned by Mono.just is the data that will reach the web page
                                return Mono.just(str);
                            });
                        })
                        //Set the monitored service name, adopt lb protocol, load balance
                        .uri("lb://user-service");
            }).build();
        }
    }
    
    

Front and back end separation scenarios, using gateway to verify token s

The first is to use global filtering to determine whether the token exists and whether it has been logged in

  1. Client sends request, login request / other request
  2. The gateway intercepts the request, the login request can be released, and other requests determine whether the token exists
    1. login request: query the database according to the carried name/password. If there is a user, store the user object in redis, and the key is the generated token; Then return the token to the client, and then each time the client accesses it, it will carry the token for access
    2. For other requests, query redis according to the carried token. If it exists, it will be released. If it does not exist, it will be directly returned to the client

Code example:

package com.pn.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

//@Component
public class TokenGlobalFilter implements GlobalFilter, Ordered {

    //Inject Redis template
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //Get the url interface of the request
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().value();

        //About to do login authentication
        if(path.contains("/doLogin")){
            //Direct release
            return chain.filter(exchange);
        }

        /*
          Get the value of request header Authorization, i.e. token:
          The front-end and back-end separate items. The front-end carries the token through the request header Authorization, and its value token will be prefixed with bear; specific
          What method to use to confirm with the front-end girl;
         */
        List<String> authList = request.getHeaders().get("Authorization");
        if(!CollectionUtils.isEmpty(authList)){
            String token = authList.get(0);
            if(!StringUtils.isEmpty(token)){
                token = token.replaceAll("bearer ", "");
                if(redisTemplate.hasKey(token)){
                    //Release
                    return chain.filter(exchange);
                }
            }
        }

        /*
          Not certified:
         */
        ServerHttpResponse response = exchange.getResponse();
        //Set the response type to application/json
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        //Response json
        Map<String, Object> data = new HashMap<>();
        data.put("code", 401);
        data.put("msg", "un_authorization");
        ObjectMapper objMapper = new ObjectMapper();
        byte[] bytes = new byte[0];
        try {
            //Convert map into byte data of json
            bytes = objMapper.writeValueAsBytes(data);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        //Assemble the byte data of json into the data buffer
        DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
        //Respond to the client
        return response.writeWith(Mono.just(dataBuffer));
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

Second: use global filter + local filter to judge

  1. Client sends request, login request / other request
  2. The gateway intercepts the request, the global filter starts to judge, the login request can be released, and other requests judge whether the token exists
    1. Configure a local post filter that belongs to login. After the login is completed, the token is generated and returned to the method of the post filter. The method of the post filter operates redis, inserts records, and sets the save time to return the token to the client
  3. All subsequent requests are globally filtered to determine whether there is a token in redis

Code example:

package com.pn.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import reactor.core.publisher.Mono;

import java.util.concurrent.TimeUnit;

@Configuration
public class GatewayConfig {

    //Inject Redis template
    @Autowired
    private StringRedisTemplate redisTemplate;

    //Configure a separate route for the login service
    //@Bean
    public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder){

        //Create route builder
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();

        //Configure routing
        return routes.route("login-service-router", r -> {
           return r.path("/doLogin")//Assertion of url interface
                    //Configure local filters
                    .filters(t -> {
                        //Configure post filter -- parameter 3: parameter 2 of Lambda expression. str is the json string responded by the / doLogin interface
                        return t.modifyResponseBody(String.class, String.class, (exchange, str) -> {
                            JSONObject jsonObj = JSON.parseObject(str);
                            //If the authentication is passed, the json string with the token saved in the / doLogin interface response
                            if(jsonObj.containsKey("token")){
                                String token = jsonObj.getString("token");
                                Integer expires_in = jsonObj.getInteger("expires_in");
                                //Save the token to redis
                                redisTemplate.opsForValue().set(token, "", expires_in, TimeUnit.SECONDS);
                            }
                            //Whether the authentication succeeds or fails, the json string responded by the / doLogin interface should be sent to the client again
                            return Mono.just(str);
                        });
                    })
                    .uri("lb://Login service "); / / url address of the service to be routed
        }).build();
    }

}

Using gateway to realize current limiting

Current limiting overview
Generally speaking, flow restriction is to limit the number of times users access resources in a period of time to reduce the pressure on the server. There are two types of flow restriction:

  1. **ip flow restriction: * * if the same ip is accessed more than 3 times in 5s, the access is restricted, and the access can be continued after a period of time.
  2. **Request flow limit: * * if the number of requests reaches the threshold within a certain period of time, the subsequent access will be directly rejected, and the access can be continued after a certain period of time; Granularity can be refined to limit the flow of requests for a url and a service.

Gateway current limiting
The Gateway has a built-in local filter RequestRateLimiterGatewayFilterFactory, which combines Redis with token bucket algorithm to limit the flow. We can use it directly.

Token bucket algorithm:

This token is just a token, not a token.

Idea overview:

  1. All requests need to get an available token before being processed.

  2. According to the size of the current limit, set to add tokens to the bucket at a certain rate.

  3. The bucket sets the maximum token placement limit. When the bucket is full, newly added tokens are discarded or rejected.

  4. After the request is reached, the token in the token bucket must be obtained first, and then other business logic can be performed with the token. After the business logic is processed,

    Delete the token directly

  5. The token bucket has a minimum limit. When the token in the bucket reaches the minimum limit, the token will not be deleted after the request is processed, so as to ensure sufficient security

    Enough current limit.

realization:

Step 1: modify the configuration file

server:
  port: 80 #Set the Gateway port -- 80
spring:
  application:
    name: gateway-80 #Set the Gateway service name -- gateway-80
#----------------Redis configuration------------------------------
  redis:
    host: 192.168.9.128 #IP of the redis server (IP of the linux on which redis is installed)
    port: 6379 #redis port
    password: mmy123 #If redis sets a password, specify the password
    database: 0 #Subscript of the database that operates redis
    #Configuration of redis connection pool
    lettuce:
      pool:
        max-active: 10 #Set the number of jedis objects in the pool
        max-idle: 10 #Set the maximum free number of jedis objects in the pool
        min-idle: 3 #Set the minimum number of jedis objects in the pool
#----------------Configuration of Gateway-------------------------
  cloud:
    gateway:
      enabled: true #Open the gateway
      #Configure routing
      routes:
        - id: search-service-router #Route id
          uri: lb://Search service # sets the URI of the service to be routed -- LB protocol, service name search service
          #Route assertion
          predicates:
            - Path=/doSearch #If the request path is / doSearch in the service, the route is available
   #******Configure a flow limiting filter (RequestRateLimiterGatewayFilterFactory) for this route******
          filters:
            #The reference name of the filter must be RequestRateLimiter
            - name: RequestRateLimiter
              args:
                #The reference name of the parser of the flow limiting key, through which you can get the flow limiting key (basis) -- ip flow limiting
                ##The parameter in {} is the method name of injecting the object into the ioc container in the second step
                key-resolver: '#{@ipKeyResolver}'
                #Average rate of token bucket filling per second
                redis-rate-limiter.replenishRate: 1
                #Total capacity of token bucket
                redis-rate-limiter.burstCapacity: 3

#Register the Gateway service to the registry -- the value is the url address of the eureka server (Registry)
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka

Step 2: create a configuration class

@Configuration
public class RequestRateLimiterConfig {
    
    //---------------------Filter RequestRateLimiterGatewayFilter current limiting configuration------------------------------------------------
    /*
      Configure the key of flow restriction (by) -- bean object:
       Interface KeyResolver:
       Mono<String> | resolve(ServerWebExchange exchange);
     */
    //@Bean
    public KeyResolver ipKeyResolver(){
       //ip current limiting
       return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }

    /*
    	Use url to limit the flow
    */
    @Bean
    public KeyResolver urlKeyResolver(){
        //url limit
        return exchange -> Mono.just(exchange.getRequest().getPath().value());
    }
}

Cross domain configuration

Configuration class adding method:

    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        UrlBasedCorsConfigurationSource source = 
new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

Tags: Java Spring Cloud gateway

Posted by AceE on Sun, 11 Sep 2022 22:47:56 +0530