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:
-
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. -
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).
-
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:
- GatewayFilter: local filter. It needs to be configured to a route to filter.
- GlobalFilter: Global filter. It does not need to configure routes. It will work on all routes during system initialization.
Configuration of global filter:
-
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; } }
-
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:
-
Step 1: create a Configuration class and mark the @ Configuration annotation
-
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
- Client sends request, login request / other request
- The gateway intercepts the request, the login request can be released, and other requests determine whether the token exists
- 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
- 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
- Client sends request, login request / other request
- 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
- 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
- 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:
- **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.
- **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:
-
All requests need to get an available token before being processed.
-
According to the size of the current limit, set to add tokens to the bucket at a certain rate.
-
The bucket sets the maximum token placement limit. When the bucket is full, newly added tokens are discarded or rejected.
-
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
-
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); } }