preface

Spring Cloud was used in this paper as 2.1.8RELEASE, version=Greenwich.SR3

This article is based on the implementation of eureka-Server, Eureka-Client, Eureka-Ribbon, Eureka-Feign, and Spring-Gataway from the previous two articles. reference

  • eureka-server
  • eureka-client
  • eureka-ribbon
  • eureka-feign
  • spring-gateway

Is art

Spring Cloud Gateway already provides many filters, such as Hystrix Gateway Filter and Prefix PathGateway filter. Interested partners can directly read Spring Cloud Gateway official website related documents or directly read the source code. However, in many cases, the built-in filter can not meet our needs, so it is very important to customize the filter. This article mainly introduces Global Filter and Gateway Filter.

Gateway Filter

Custom filters need to implement GatewayFilter and Ordered. The GatewayFilter is mainly used to realize the specific logic of customization. The getOrder() method in Ordered is to set the priority level of the filter. The higher the value is, the lower the priority level is.

1.1 create a Filter

package spring.cloud.demo.spring.gateway.filter;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

public class MyGatewayFilter implements GatewayFilter.Ordered {

    private static final Log log = LogFactory.getLog(MyGatewayFilter.class);

    private static final String TIME = "Time";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        exchange.getAttributes().put(TIME, System.currentTimeMillis());
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    Long start = exchange.getAttribute(TIME);
                    if(start ! =null) {
                        log.info("exchange request uri:" + exchange.getRequest().getURI() + ", Time:" + (System.currentTimeMillis() - start) + "ms"); }})); }@Override
    public int getOrder(a) {
        returnOrdered.LOWEST_PRECEDENCE; }}Copy the code

When the request arrives, we put an attribute TIME into ServerWebExchange, and the value of the attribute is the number of milliseconds of the current TIME. Then after the request ends, we take out the number of requested TIME and make a difference with the number of current TIME to get the number of elapsed TIME.

How do you distinguish “Pre” from “post”?

  • Pre is the Chain. Filter (Exchange) part.
  • Post is the then() part.

1.2 Adding Filter to a Chain

package spring.cloud.demo.spring.gateway.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 spring.cloud.demo.spring.gateway.filter.MyGatewayFilter;

@Configuration
public class RoutesConfig {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder){
        return routeLocatorBuilder.routes().route(r -> r.path("/ribbon/**")
                .filters(f -> f.stripPrefix(1)
                        .filter(new MyGatewayFilter()) // Add a custom filter
                        .addRequestHeader("X-Response-Default-Foo"."Default-Bar"))
                .uri("lb://EUREKA-RIBBON")
                .order(0)
                .id("ribbon-route") ).build(); }}Copy the code

1.2 Starting related Services

Start the eureka – server, eureka – client, eureka – ribbon, spring – gateway related services, visit http://localhost:8100/ribbon/sayHello address, the page displays the results are as follows:

2.1 create GlobalFilter

package spring.cloud.demo.spring.gateway.filter;

import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;


/** * Global filter * check token */
public class MyGlobalFilter implements GlobalFilter.Ordered {

    private static final String TOKEN = "token";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String parm = exchange.getRequest().getQueryParams().getFirst(TOKEN);
        if (StringUtils.isBlank(parm)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

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

2.2 add Bean

Add MyGlobalFilter to the Bean

package spring.cloud.demo.spring.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.cloud.demo.spring.gateway.filter.MyGlobalFilter;

/ * * *@auther: fujie.feng
 * @DateT: the 2019-10-12 * /
@Configuration
public class RoutesConfig {

    /** * global filter *@return* /
    @Bean
    public MyGlobalFilter myGlobalFilter(a) {
        return newMyGlobalFilter(); }}Copy the code

This is just a simple simulation, if you are interested you can try to pull all the parameters out and parse them yourself (you can use reflection to do this).

2.3 Starting the Service

Restart services, visit http://localhost:8100/ribbon/sayHello, shows as follows:

The 16:20:00 2019-10-21. 15322-478 the INFO [ctor - HTTP - nio - 2] c.net flix. Config. ChainedDynamicProperty: Flipping the property: EUREKA-RIBBON.ribbon.ActiveConnectionsLimit to use NEXT property: Niws. Loadbalancer. AvailabilityFilteringRule. ActiveConnectionsLimit 16:20:00 = 2147483647, 2019-10-21. 15322-480 the INFO [ctor-http-nio-2] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client EUREKA-RIBBON initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=EUREKA-RIBBON,current list of Servers=[eureka1.server.com:8901],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: Tripped count: 0; Circuit breaker tripped count: 0; Active Connections per server: 0.0; tripped tripped count: 0;  },Server stats: [[Server:eureka1.server.com:8901; Zone:defaultZone; Total Requests:0; Successive connection failure:0;  Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; Total Failure Count in last (1000) mSECs :0; Average Resp time:0.0; Total Failure count in last (1000) mSECs :0; 90 the percentile resp time: 0.0; 95 the percentile resp time: 0.0; min resp time: 0.0; Max resp time: 0.0. Stddev resp time: 0.0]]} ServerList:org.springframework.cloud.net flix. Ribbon. Eureka. DomainExtractingServerList @ 40585976 The 2019-10-21 16:20:01. 15322-293 the INFO [ctor - HTTP - nio - 8] S.C.D.S.G ateway. Filter. MyGatewayFilter: exchange request uri:http://localhost:8100/sayHello?token=xxx, Time: 23 ms 16:20:01 2019-10-21. 15322-467 the INFO [erListUpdater - 0] c.net flix. Config. ChainedDynamicProperty: Flipping property: EUREKA-RIBBON.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647Copy the code

Quote from the official website: The GlobalFilter interface has the same signature as GatewayFilter. These are special filters that are conditionally applied to all routes. (This interface and usage are subject to change in future milestones). Note There will be some changes to GlobalFilter in future releases.

conclusion

At this point, the two ways of customizing filters are simply implemented. The same can be done at Feign.

eggs

In the previous article, the configuration file had this configuration:

filters:
  - StripPrefix=1
  - AddResponseHeader=X-Response-Default-Foo, Default-Bar
Copy the code

The StripPrefix and AddResponseHeader configurations are actually gatewayFilterFactories for the two filters, and the Filter factory is shown next, which is more flexible.

1.1 Creating a Custom Filter Factory

package spring.cloud.demo.spring.gateway.factory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;

/** * Custom filter factory */
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {

    private static final Log log = LogFactory.getLog(MyGatewayFilterFactory.class);

    private static final String PARAMS = "myParams";

    private static final String START_TIME = "startTime";

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

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

    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain) -> {
            exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
            return chain.filter(exchange).then(
                    Mono.fromRunnable(() -> {
                        Long startTime = exchange.getAttribute(START_TIME);
                        if (startTime == null) {
                            return;
                        }
                        StringBuilder sb = new StringBuilder();
                        sb.append("exchange request uri:" + exchange.getRequest().getURI() + ",");
                        sb.append("Time:" + (System.currentTimeMillis() - startTime) + "ms.");
                        if (config.isMyParams()) {
                            sb.append("params:"+ exchange.getRequest().getQueryParams()); } log.info(sb.toString()); })); }); }/** * Configuration parameter class */
    public static class Config {

        private boolean myParams;

        public boolean isMyParams(a) {
            return myParams;
        }

        public void setMyParams(boolean myParams) {
            this.myParams = myParams; }}}Copy the code

Note: when we inherit AbstractGatewayFilterFactory to pass custom Config classes to the parent class, otherwise an error.

1.2 Adding custom factory Beans

package spring.cloud.demo.spring.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.cloud.demo.spring.gateway.factory.MyGatewayFilterFactory;

@Configuration
public class FilterFactory {

    @Bean
    public MyGatewayFilterFactory myGatewayFilterFactory(a) {
        return newMyGatewayFilterFactory(); }}Copy the code

1.3 modify application. Yml

server:
  port: 8100
spring:
  application:
    name: spring-gateway
  cloud:
      gateway:
        discovery:
          locator:
            enabled: true # Enable the function of automatically creating routes based on the serviceId in the service center
        default-filters:
          - My=true
        routes:
          - id: ribbon-route
            uri: lb://EUREKA-RIBBON
            order: 0
            predicates:
              - Path=/ribbon/**
            filters:
              - StripPrefix=1 Reference StripPrefixGatewayFilterFactory # remove prefixes, concrete realization
              - AddResponseHeader=X-Response-Default-Foo, Default-Bar
          - id: feign-route
            uri: lb://EUREKA-FEIGN
            order: 0
            predicates:
              - Path=/feign/**
            filters:
              - StripPrefix=1
              - AddResponseHeader=X-Response-Default-Foo, Default-Bar


eureka:
  instance:
    hostname: eureka1.server.com
    lease-renewal-interval-in-seconds: 5
    lease-expiration-duration-in-seconds: 10
  client:
    service-url:
      defaultZone: http://eureka1.server.com:8701/eureka/,http://eureka2.server.com:8702/eureka/,http://eureka3.server.com:8703/eureka/
Copy the code

Default-filters: -my =true mainly adds this configuration.

1.4 Starting the Service

Go to http://localhost:8100/ribbon/sayHello? Token = XXX, if:

The 17:40:20 2019-10-21. 18059-191 the INFO [ctor - HTTP - nio - 2] c.net flix. Config. ChainedDynamicProperty: Flipping the property: EUREKA-RIBBON.ribbon.ActiveConnectionsLimit to use NEXT property: Niws. Loadbalancer. AvailabilityFilteringRule. ActiveConnectionsLimit 17:40:20 = 2147483647, 2019-10-21. 18059-192 the INFO [ctor-http-nio-2] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client EUREKA-RIBBON initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=EUREKA-RIBBON,current list of Servers=[eureka1.server.com:8901],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: Tripped count: 0; Circuit breaker tripped count: 0; Active Connections per server: 0.0; tripped tripped count: 0;  },Server stats: [[Server:eureka1.server.com:8901; Zone:defaultZone; Total Requests:0; Successive connection failure:0;  Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; Total Failure Count in last (1000) mSECs :0; Average Resp time:0.0; Total Failure count in last (1000) mSECs :0; 90 the percentile resp time: 0.0; 95 the percentile resp time: 0.0; min resp time: 0.0; Max resp time: 0.0. Stddev resp time: 0.0]]} ServerList:org.springframework.cloud.net flix. Ribbon. Eureka. 46 c172ce DomainExtractingServerList @ The 17:40:20 2019-10-21. 18059-583 the INFO [ctor - HTTP - nio - 7] S.C.D.S.G.F.M yGatewayFilterFactory: exchange request uri:http://localhost:8100/ribbon/sayHello?token=xxx,Time:582ms.params:{token=[xxx]} 2019-10-21 17:40:21. 18059-181 the INFO [erListUpdater - 0] c.net flix. Config. ChainedDynamicProperty: Flipping the property: EUREKA-RIBBON.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647Copy the code

conclusion

The top interface for a filter factory is the GatewayFilterFactory, We can directly inherited their two abstract class AbstractGatewayFilterFactory and AbstractNameValueGatewayFilterFactory to simplify the development. Difference between AbstractGatewayFilterFactory is to accept a parameter, AbstractNameValueGatewayFilterFactory is accepts two parameters, such as: – AddResponseHeader=X-Response-Default-Foo, Default-Bar

conclusion

This article introduces the simple use of GatewayFilter, GlobalFilter and GatewayFilterFactory. I believe that you have a simple understanding of Spring Cloud Gateway.

The code address

Making the address


  • Eureka Server Service Registry tutorial for Spring Cloud 2.x
  • Eureka Client Service Provider tutorial for Spring Cloud 2.x
  • Spring Cloud 2.x Ribbon Service Discovery Tutorial (with Integrated Hystrix Fuse)
  • Feign Service Discovery Tutorial with Integrated Hystrix Circuit breaker in Spring Cloud 2.x
  • Spring Cloud 2.x Zuul Routing Gateway Tutorial
  • Spring Cloud 2.x Config Distributed Configuration Center tutorial
  • Spring Cloud 2.x Hystrix Dashboard Circuit breaker tutorial
  • Spring Cloud 2.x Gateway Routing Gateway tutorial
  • Gateway Custom filters tutorial for Spring Cloud 2.x

  • Writing is not easy, reprint please indicate the source, like small partners can pay attention to the public number to view more like the article.
  • Contact: [email protected]
  • QQ:95472323
  • wx:ffj2000