Reprint please indicate source: www.fangzhipeng.com This article comes from Fang Zhipeng’s blog

As described in the previous article, Gateway Predict determines which route to process the request. The pre filter is used to process the request, and the post filter is used to process the response.

Function and lifecycle of filter

From the filter workflow point, it can be seen that filter plays a very important role. Filter of “Pre” type can be used for parameter verification, permission verification, traffic monitoring, log output, protocol conversion, etc. Filter of “POST” type can be used for response content, response header modification, log output, traffic monitoring, etc. First of all, we need to make clear why the gateway layer is needed, and then we have to talk about the role of filter.

role

When we have multiple services, such as user-service, goods-service, and sales-service in the figure below, when the client requests the Api of each service, each service needs to do the same things, such as authentication, traffic limiting, and log output.

Is there a way to do this repetitive work better? The answer is yes. Add an Api Gatewat service with global permission control, traffic limiting and log output on the top layer of the microservice, and then forward the request to the specific business service layer. The Api Gateway service acts as a service boundary, and external requests to access the system must first pass through the Gateway layer.

The life cycle

Spring Cloud Gateway is similar to Zuul in that it has “Pre” and “post” filters. The request from the client passes the Pre filter and is forwarded to a specific service, such as user-service in the figure above. After receiving the response from the service, the request is processed by the Post filter and sent to the client.

Different from Zuul, the filter can be divided into “Pre” and “Post”. In Spring Cloud Gateway, the filter can be divided into two other types from the scope of application. One is Gateway filter for a single route. It is written in a configuration file similar to Predict; The other is a global Gateway filer for all routes. Let’s look at these two filters in terms of scope dimensions.

gateway filter

Filters allow you to modify an incoming HTTP request or an outgoing HTTP response in some way. Filters can be restricted to specific request paths. The Spring Cloud Gateway contains a number of built-in GatewayFilter factories.

The GatewayFilter Factory is similar to the Predicate Factory described in the previous article. The Predicate Factory is configured in the application. Yml configuration file. And do not need to write all the name of the class, such as AddRequestHeaderGatewayFilterFactory need only write AddRequestHeader in the configuration file, rather than the full name of the class. The GatewayFilter Factory configured in the configuration file will eventually be handled by the corresponding filter Factory class.

A list of filter factories built into the Spring Cloud Gateway is as follows:

Now pick a few common filter factory, each filter factory in official documents detailed use cases are given, if not clear can org. Springframework. Cloud. Gateway. Filter. The factory to see the source code of each filter factory.

AddRequestHeader GatewayFilter Factory

Create project, introduce related dependencies, including Spring Boot version 2.0.5, Spring Cloud version Finchley, gateway dependencies as follows:

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

Copy the code

In the project configuration file, add the following configuration:

server: port: 8081 spring: profiles: active: add_request_header_route --- spring: cloud: gateway: routes: - id: add_request_header_route uri: http://httpbin.org:80/get filters: - AddRequestHeader=X-Request-Foo, Bar predicates: T17 - After = 2017-01-20:42:47. 789 - from America/Denver profiles: add_request_header_routeCopy the code

In the preceding configuration, the project startup port is 8081 and the configuration file is add_request_header_route. In the add_request_header_route configuration, the ROter ID is add_request_header_route. Routing address for http://httpbin.org:80/get, the router has AfterPredictFactory, There is a filter for AddRequestHeaderGatewayFilterFactory AddRequestHeader written (agreement), AddRequestHeader filter factory will add a Request header at Request header, called X – Request – Foo, The value of the Bar. How to verify AddRequestHeaderGatewayFilterFactory works, look at the source, AddRequestHeaderGatewayFilterFactory source code is as follows:


public class AddRequestHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {

	@Override
	public GatewayFilter apply(NameValueConfig config) {
		return (exchange, chain) -> {
			ServerHttpRequest request = exchange.getRequest().mutate()
					.header(config.getName(), config.getValue())
					.build();

			returnchain.filter(exchange.mutate().request(request).build()); }; }}Copy the code

Create a new ServerHttpRequest from the old ServerHttpRequest, add a request header to the new ServerWebExchange, and submit the filter chain to continue filtering.

Curl = curl = curl = curl


curl localhost:8081

Copy the code

Finally shows the request from httpbin.org:80/get, the response…

{ "args": {}, "headers": { "Accept": "*/*", "Connection": "close", "Forwarded": "proto=http; host=\"localhost:8081\"; For = \ "0:0:0:0:0:0:0:1:56 248 \" ", "the Host" : "httpbin.org", "the user-agent" : "curl / 7.58.0", "X - Forwarded - Host" : "Localhost: 8081", "X - Request - Foo" : "Bar"}, "origin" : "0:0:0:0:0:0:1-0, 210.22.21.66", "url" : "http://localhost:8081/get" }Copy the code

As you can see from the response above, a Request header like X-request-foo was added to the Request header, and the AddRequestHeader filter factory configured in the configuration file took effect.

Similar to the AddRequestHeader filter factory is the AddResponseHeader filter factory, which will not be repeated here.

RewritePath GatewayFilter Factory

One of the most powerful features in Nginx service development is the ability to override paths, which Spring Cloud Gateway provides by default, but Zuul does not. Add the following configuration to the configuration file:


spring:
  profiles:
    active: rewritepath_route
---
spring:
  cloud:
    gateway:
      routes:
      - id: rewritepath_route
        uri: https://blog.csdn.net
        predicates:
        - Path=/foo/**
        filters:
        - RewritePath=/foo/(?<segment>.*), /$\{segment}
  profiles: rewritepath_route

Copy the code

In the above configuration, all paths starting with /foo/** will match the configured router and the filter logic will be executed. In this case, the RewritePath filter factory is configured, which will set /foo/(? .*) rewrite to {segment} and forward to https://blog.csdn.net. Such as on a web page request localhost: 8081 / foo/forezp, will forward the request to the https://blog.csdn.net/forezp page at this time, such as on a web page request localhost: 8081 / foo/forezp / 1, page 404, Because there is no https://blog.csdn.net/forezp/1 page.

Custom filter

Spring Cloud Gateway has 19 powerful filter factories built in to meet the needs of many scenarios, so it is possible to customize your own filters. In the Spring Cloud Gateway, the filter needs to implement the GatewayFilter and Orderedinterfaces. Write a RequestTimeFilter as follows:


public class RequestTimeFilter implements GatewayFilter.Ordered {

    private static final Log log = LogFactory.getLog(GatewayFilter.class);
    private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";

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

        exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
                    if(startTime ! =null) {
                        log.info(exchange.getRequest().getURI().getRawPath() + ":" + (System.currentTimeMillis() - startTime) + "ms"); }})); }@Override
    public int getOrder(a) {
        return 0; }}Copy the code

In the above code, the int getOrder() method in Ordered sets the priority of the filter; higher values lower the priority. There is also a filterI(Exchange,chain) method, in which the start time of the request is recorded and stored in ServerWebExchange, a filter of type “pre”, The run() method equivalent to the “post” filter in the inner class of chain.filter prints the time consumed by the request. Then register the filter with the Router as follows:

    @Bean
    public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
        // @formatter:off
        return builder.routes()
                .route(r -> r.path("/customer/**")
                        .filters(f -> f.filter(new RequestTimeFilter())
                                .addResponseHeader("X-Response-Default-Foo"."Default-Bar"))
                        .uri("http://httpbin.org:80/get")
                        .order(0)
                        .id("customer_filter_router")
                )
                .build();
        // @formatter:on
    }


Copy the code

Curl = curl = curl = curl

 curl localhost:8081/customer/123

Copy the code

In the application console output the following request information log:

The 15:02:20 2018-11-16. 20488-177 the INFO [ctor - HTTP - nio - 3] O.S.C loud. Gateway. Filter. GatewayFilter: / customer / 123:152 msCopy the code

Custom filter factory

In the custom filters above, is there a way to customize the filter factory class? This allows you to configure the filter in the configuration file. Now you need to implement a filter factory, at the time of printing, you can set parameters to determine whether to print parameters. The GatewayFilterFactory hierarchy is as follows:

Top interface is GatewayFilterFactory filter factory, we can directly inherited the two abstract classes to simplify the development AbstractGatewayFilterFactory and AbstractNameValueGatewayFilterFactory, The difference between these two abstract classes is that the former takes one argument (like StripPrefix and the one we created) and the latter takes two arguments (like AddResponseHeader).

Top interface is GatewayFilterFactory filter factory, there are two two relatively close to the concrete realization of the abstract class, AbstractGatewayFilterFactory and AbstractNameValueGatewayFilterFactory respectively, These two kind of the former receives a parameter, such as its implementation class RedirectToGatewayFilterFactory; The latter receives two parameters, such as its implementation class AddRequestHeaderGatewayFilterFactory class. Now you need to print will be asked to log, you need to use a parameter, then can consult the RedirectToGatewayFilterFactory way.


public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> {


    private static final Log log = LogFactory.getLog(GatewayFilter.class);
    private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";
    private static final String KEY = "withParams";

    @Override
    public List<String> shortcutFieldOrder(a) {
        return Arrays.asList(KEY);
    }

    public RequestTimeGatewayFilterFactory(a) {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
            return chain.filter(exchange).then(
                    Mono.fromRunnable(() -> {
                        Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
                        if(startTime ! =null) {
                            StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
                                    .append(":")
                                    .append(System.currentTimeMillis() - startTime)
                                    .append("ms");
                            if (config.isWithParams()) {
                                sb.append(" params:").append(exchange.getRequest().getQueryParams()); } log.info(sb.toString()); }})); }; }public static class Config {

        private boolean withParams;

        public boolean isWithParams(a) {
            return withParams;
        }

        public void setWithParams(boolean withParams) {
            this.withParams = withParams; }}}Copy the code

In the code above, apply(Config Config) creates a GatewayFilter anonymous class. The implementation logic is the same as before, but the logic is added whether to print the request parameters, and this logic switch is config.isWithParams(). The static inner class Config is designed to receive the Boolean parameter service, where the variable name can be written at will, overriding the List shortcutFieldOrder() method. .

The class constructor must call the parent constructor to pass the Config type, otherwise the ClassCastException will be reported

Finally, you need to in the project startup file Application class, to the Srping Ioc container registered RequestTimeGatewayFilterFactory Bean class.


    @Bean
    public RequestTimeGatewayFilterFactory elapsedGatewayFilterFactory(a) {
        return new RequestTimeGatewayFilterFactory();
    }

Copy the code

You can then configure the following in the configuration file:

spring: profiles: active: elapse_route --- spring: cloud: gateway: routes: - id: elapse_route uri: http://httpbin.org:80/get filters: - RequestTime=false predicates: T17 - After = 2017-01-20:42:47. 789 - from America/Denver profiles: elapse_routeCopy the code

To start the project, go to localhost:8081? Name =forezp, which can be seen on the console. The log outputs the time consumed by the request and the request parameters.

global filter

Spring Cloud Gateway is divided into GatewayFilter and GlobalFilter according to scope. The differences are as follows:

  • GatewayFilter: need to spring. Cloud. Routes. Filters configured under the specific route, the only role in the current routing on or through the spring. The cloud. The default – filters configured in the global, effect on all routes

  • GlobalFilter : The global filter, which does not need to be configured in the configuration file, acts on all routes and is packaged into a filter identified by the GatewayFilterChain through the GatewayFilterAdapter. It is the core filter for the request service and the URI of the route is converted into the request address of the real service. No configuration is required. It is loaded during system initialization and applies to each route.

GlobalFilter built into the Spring Cloud Gateway framework is as follows:

Each GlobalFilter in the figure above works on each router and meets most of the requirements. However, if you encounter business customization, you may need to write a GlobalFilter that meets your needs. The following example shows how to write your own GlobalFilter that verifies whether a request contains a token and does not forward a route if the request does not contain a token. Otherwise, normal logic is followed. The code is as follows:


public class TokenFilter implements GlobalFilter.Ordered {

    Logger logger=LoggerFactory.getLogger( TokenFilter.class );
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (token == null || token.isEmpty()) {
            logger.info( "token is empty..." );
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder(a) {
        return -100; }}Copy the code

The TokenFilter above needs to implement the GlobalFilter and Ordered interfaces, much like implementing GatewayFilter. Then obtain ServerHttpRequest according to ServerWebExchange, and then according to whether ServerHttpRequest contains parameter token, if not, complete the request, terminate forwarding, otherwise execute normal logic.

Then you need to inject TokenFilter into the Spring Ioc container in the project’s startup class as follows:


@Bean
public TokenFilter tokenFilter(a){
        return new TokenFilter();
}

Copy the code

Using curl to start a project

 curl localhost:8081/customer/123
 
Copy the code

As you can see, please do not forward, the request is aborted, and the following log is printed on the console:

2018-11-16 15:30:13.543 INFO 19372 -- [cTOR-http-nio-2] gateway.TokenFilter: Token is empty...Copy the code

The above log shows that the request entered logic without passing the “token”.

conclusion

This article describes the filters in the Spring Cloud Gateway, including GatewayFilter and GlobalFilter. Start with the built-in filters for official documents, and then explain custom GatewayFilter, GatewayFilterFactory, and custom GlobalFilter. There are many built-in filters that are not covered, such as the limited-flow filter, which I think is more important and of concern to everyone, and will be covered in a later article.

The resources

Cloud. Spring. IO/spring – clou…

www.jianshu.com/p/eb3a67291…

Blog.csdn.net/qq_36236890…

Windmt.com/2018/05/08/…

Download the source code

Github.com/forezp/Spri…



Scan and support the author

(Please indicate the author and source of the reprint article fang Zhipeng’s blog)