Учебное пособие по пользовательскому фильтру шлюза версии Spring Cloud 2.x

Spring Cloud

предисловие

В этой статье используется облако Spring. Это статья 2.1.8RELEASE, version=Greenwich.SR3.

Эта статья основана на реализации двух предыдущих статей: eureka-server, eureka-client, eureka-ribbon, eureka-feign и spring-gataway. Ссылаться на

обобщение

Spring Cloud Gateway уже предоставляет множество фильтров, Hystrix Gateway Filter, Prefix PathGateway Filter и т. д. Заинтересованные партнеры могут напрямую прочитать соответствующие документы на официальном сайте Spring Cloud Gateway или непосредственно прочитать исходный код. Но во многих случаях встроенные фильтры не могут удовлетворить наши потребности, поэтому пользовательские фильтры очень важны. В этой статье в основном представлены глобальный фильтр (Global Filter) и локальный фильтр (Gateway Filter).

Gateway Filter

Пользовательские фильтры должны реализовывать GatewayFilter и Ordered. Среди них GatewayFilter в основном используется для реализации определенной логики настройки, а метод getOrder() в Ordered используется для установки уровня приоритета фильтра, чем больше значение, тем ниже уровень приоритета.

1.1 Создать фильтр

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() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

При поступлении запроса мы ставим атрибут TIME в ServerWebExchange, а значением атрибута является количество миллисекунд текущего времени, а затем после завершения запроса мы вынесем запрошенное время и сделаем разницу между текущим время и получить прошедшее время.

Как отличить "до" от "после"?

  • pre — это часть chain.filter(exchange).
  • post — это часть then().

1.2 Добавить фильтр в цепочку

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()) //增加自定义filter
                        .addRequestHeader("X-Response-Default-Foo", "Default-Bar"))
                .uri("lb://EUREKA-RIBBON")
                .order(0)
                .id("ribbon-route")
        ).build();
    }
}

1.2 Запуск связанных служб

Запустите службы, связанные с eureka-server, eureka-client, eureka-ribbon, spring-gateway, посетите адрес http://localhost:8100/ribbon/sayHello, на странице отобразятся следующие результаты:

В этот момент я открываю консоль и вижу вывод журнала как:

2.1 Создать глобальный фильтр

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;


/**
 * 全局过滤器
 * 校验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() {
        return 1;
    }
}

2.2 Добавить бин

Добавьте MyGlobalFilter в компонент

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: 2019-10-12
 */
@Configuration
public class RoutesConfig {

    /**
     * 全局filter
     * @return
     */
    @Bean
    public MyGlobalFilter myGlobalFilter() {
        return new MyGlobalFilter();
    }
}

Это всего лишь простая симуляция, если вам интересно, вы можете попробовать взять и разобрать все параметры самостоятельно (для этого можно использовать рефлексию).

2.3 Запустите службу

Перезапустите службу и посетите http://localhost:8100/ribbon/sayHello. Отображение выглядит следующим образом:

Я вижу, что доступ к дисплею недействителен, мы добавляем token=xxx в запрос, и дисплей выглядит следующим образом:
Это чтобы увидеть нормальную отдачу. Вывод журнала выглядит следующим образом:

2019-10-21 16:20:00.478  INFO 15322 --- [ctor-http-nio-2] c.netflix.config.ChainedDynamicProperty  : Flipping property: EUREKA-RIBBON.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2019-10-21 16:20:00.480  INFO 15322 --- [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: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.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;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@40585976
2019-10-21 16:20:01.293  INFO 15322 --- [ctor-http-nio-8] s.c.d.s.gateway.filter.MyGatewayFilter   : exchange request uri:http://localhost:8100/sayHello?token=xxx, Time:23ms
2019-10-21 16:20:01.467  INFO 15322 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty  : Flipping property: EUREKA-RIBBON.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647

Цитируя исходный текст официального сайта: Интерфейс GlobalFilter имеет ту же подпись, что и GatewayFilter. Это специальные фильтры, которые условно применяются ко всем маршрутам (этот интерфейс и использование могут быть изменены в будущих этапах). Объясните, что GlobalFilter будет иметь еще немного в будущих версиях Разнообразие.

Суммировать

Пока просто реализованы два способа настройки фильтра. Точно так же можно проверить в притворстве.

пасхальные яйца

В предыдущей статье в конфигурационном файле есть такая конфигурация:

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

Две конфигурации StripPrefix и AddResponseHeader на самом деле являются двумя фабриками фильтров (GatewayFilterFactory). Далее будет представлено использование фабрик фильтров, что является относительно более гибким.

1.1 Создание собственной фабрики фильтров

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;

/**
 * 自定义过滤器工厂
 */
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() {
        super(Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        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());
                    })
            );
        });
    }

    /**
     * 配置参数类
     */
    public static class Config {

        private boolean myParams;

        public boolean isMyParams() {
            return myParams;
        }

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

Примечание. Когда мы наследуем AbstractGatewayFilterFactory, мы должны передать пользовательский класс Config родительскому классу, иначе будет сообщено об ошибке.

1.2 Добавить пользовательский фабричный компонент

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() {
        return new MyGatewayFilterFactory();
    }
}

1.3 Изменить application.yml

server:
  port: 8100
spring:
  application:
    name: spring-gateway
  cloud:
      gateway:
        discovery:
          locator:
            enabled: true # 开启通过服务中心的自动根据 serviceId 创建路由的功能
        default-filters:
          - My=true
        routes:
          - id: ribbon-route
            uri: lb://EUREKA-RIBBON
            order: 0
            predicates:
              - Path=/ribbon/**
            filters:
              - StripPrefix=1 #去掉前缀,具体实现参考StripPrefixGatewayFilterFactory
              - 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/

default-filters:- My=true в основном добавляет к этой конфигурации.

1.4 Запуск службы

Посетите http://localhost:8100/ribbon/sayHello?token=xxx и отобразите, если:

Результат вывода журнала:

2019-10-21 17:40:20.191  INFO 18059 --- [ctor-http-nio-2] c.netflix.config.ChainedDynamicProperty  : Flipping property: EUREKA-RIBBON.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2019-10-21 17:40:20.192  INFO 18059 --- [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: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.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;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@46c172ce
2019-10-21 17:40:20.583  INFO 18059 --- [ctor-http-nio-7] s.c.d.s.g.f.MyGatewayFilterFactory       : exchange request uri:http://localhost:8100/ribbon/sayHello?token=xxx,Time:582ms.params:{token=[xxx]}
2019-10-21 17:40:21.181  INFO 18059 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty  : Flipping property: EUREKA-RIBBON.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647

Суммировать

Интерфейс верхнего уровня фабрики фильтров — GatewayFilterFactory, и мы можем напрямую наследовать два их абстрактных класса — AbstractGatewayFilterFactory и AbstractNameValueGatewayFilterFactory, чтобы упростить разработку. Разница в том, что AbstractGatewayFilterFactory принимает один параметр, AbstractNameValueGatewayFilterFactory принимает два параметра, например: AddResponseHeader=X-Response-Default-Foo, Default-Bar

Эпилог

В этой статье рассказывается о простом использовании GatewayFilter, GlobalFilter и GatewayFilterFactory, Я считаю, что мои друзья имеют простое представление о Spring Cloud Gateway.

кодовый адрес

адрес гитхаба


Каталог «Spring Cloud 2.X Xiaobai Tutorial»
  • Писать не просто, просьба указывать источник для перепечатки, а друзья, которым понравилось, могут подписаться на официальный аккаунт, чтобы увидеть больше понравившихся статей.
  • Контакт: 4272231@163.com
  • QQ:95472323
  • wx:ffj2000