Spring Cloud Gateway 4 自定義Filter


Spring Cloud Gateway 自定義Filter

Spring Cloud Gateway 的Filter分為GatewayFilter和GlobalFilter兩種,二者區別如下

  • GatewayFilter : 需要通過spring.cloud.routes.filters 配置在具體路由下,只作用在當前路由上或通過spring.cloud.default-filters配置在全局,作用在所有路由上
  • GlobalFilter : 全局過濾器,不需要在配置文件中配置,作用在所有的路由上,最終通過GatewayFilterAdapter包裝成GatewayFilterChain可識別的過濾器,它為請求業務以及路由的URI轉換為真實業務服務的請求地址的核心過濾器,不需要配置,系統初始化時加載,並作用在每個路由上。

Gateway Filter

Gateway filter 是從 Web Filter中復制來的,相當於一個Filter過濾器,可以對訪問的URL進行過濾,進行橫切面處理,應用場景包括超時、安全等。

Global Filter

Spring Cloud Gateway 定義了Global filter的接口,讓我們可以自定義實現自己的Glabl Filter,Glabl Filter是一個全局的Filter,作用於所有的路由。

自定義GatewayFilter

創建一個自定的filter,功能:打印請求的耗時

/**
 * 自定義GatewayFilter
 * 打印一條請求下的耗時
 *  
 * @author chengluchao
 */
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() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

代碼解析:

  • Ordered中的int getOrder()方法是來給過濾器設定優先級別的,值越大則優先級越低。
  • 還有有一個filterI(exchange,chain)方法,在該方法中,先記錄了請求的開始時間,並保存在ServerWebExchange中,此處是一個“pre”類型的過濾器,然后再chain.filter的內部類中的run()方法中相當於"post"過濾器,在此處打印了請求所消耗的時間。

然后將該過濾器注冊到router中,代碼如下:

@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route(r -> r.path("/demo/**")
                    .filters(f -> f.filter(new RequestTimeFilter()))
                    .uri("http://httpbin.org:80/get")
                    .order(0)
                    .id("customer_filter_router")
            )
            .build();
}

執行程序,會打印出方法的耗時

自定義過濾器工廠

過濾器工廠的頂級接口是GatewayFilterFactory,我們可以直接繼承它的兩個抽象類來簡化開發AbstractGatewayFilterFactory和AbstractNameValueGatewayFilterFactory,這兩個抽象類的區別就是前者接收一個參數(像StripPrefix和我們創建的這種),后者接收兩個參數(像AddResponseHeader)。

image

做一個可配置是否開啟的打印請求耗時的過濾器

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 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() {
        return Arrays.asList(KEY);
    }

    public RequestTimeGatewayFilterFactory() {
        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() {
            return withParams;
        }

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

    }
}

在上面的代碼中 apply(Config config)方法內創建了一個GatewayFilter的匿名類,具體的實現邏輯跟之前一樣,只不過加了是否打印請求參數的邏輯,而這個邏輯的開關是config.isWithParams()。靜態內部類類Config就是為了接收那個boolean類型的參數服務的,里邊的變量名可以隨意寫,但是要重寫List shortcutFieldOrder()這個方法。

需要注意的是,在類的構造器中一定要調用下父類的構造器把Config類型傳過去,否則會報ClassCastException

最后,需要在工程的啟動文件Application類中,向Srping Ioc容器注冊RequestTimeGatewayFilterFactory類的Bean。

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

配置項:

spring:
  cloud:
    gateway:
      routes:
      - id: elapse_route
        uri: http://httpbin.org:80/get
        filters:
        - RequestTime=false
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]
  profiles: elapse_route

現在可以通過配置來決定是否開啟打印時間的日志

自定義Global Filter

該GlobalFilter會校驗請求中是否包含了“token”,如何不包含請求參數“token”則不轉發路由,否則執行正常的邏輯。代碼如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

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().getHeaders().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() {
        return -100;
    }
}

在上面的TokenFilter需要實現GlobalFilter和Ordered接口,這和實現GatewayFilter很類似。然后根據ServerWebExchange獲取ServerHttpRequest,然后根據ServerHttpRequest中是否含有參數token,如果沒有則完成請求,終止轉發,否則執行正常的邏輯。

然后需要將TokenFilter在工程的啟動類中注入到Spring Ioc容器中,代碼如下:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM