網關常常須要對路由請求進行過濾,進行一些操做,如鑑權以後構造頭部之類的,過濾的種類不少,如增長請求頭、增長請求 參數 、增長響應頭和斷路器等等功能,這就用到了Spring Cloud Gateway 的 Filter。html
當咱們有不少個服務時,好比下圖中的user-service、goods-service、sales-service等服務,客戶端請求各個服務的Api時,每一個服務都須要作相同的事情,好比鑑權、限流、日誌輸出等。java
對於這樣重複的工做,能夠在微服務的上一層加一個全局的權限控制、限流、日誌輸出的Api Gateway服務,而後再將請求轉發到具體的業務服務層。這個Api Gateway服務就是起到一個服務邊界的做用,外接的請求訪問系統,必須先經過網關層。git
Spring Cloud Gateway 的 Filter 的生命週期不像 Zuul 的那麼豐富,它只有兩個:「pre」 和 「post」。程序員
Spring Cloud Gateway 的 Filter 從做用範圍可分爲另外兩種GatewayFilter 與 GlobalFilter。github
過濾器容許以某種方式修改傳入的HTTP請求或傳出的HTTP響應。過濾器的做用域爲特定路由。Spring Cloud Gateway包含許多內置的GatewayFilter工廠。web
官方文檔都給出了這些過濾器工廠詳細的使用案例,在這裏咱們講解2個來演示下使用。spring
application.yml以下:shell
spring: cloud: gateway: routes: - id: add_request_header_route uri: http://httpbin.org:80/get filters: - AddRequestHeader=X-Request-Foo, Bar predicates: - Method=GET
過濾器工廠會在匹配的請求頭加上一對請求頭,名稱爲X-Request-Foo,值爲Bar。json
###RewritePath GatewayFilter Factory
在Nginx服務啓中有一個很是強大的功能就是重寫路徑,Spring Cloud Gateway默認也提供了這樣的功能,這個功能是Zuul沒有。markdown
application.yml以下:
spring: cloud: gateway: routes: - id: rewritepath_route uri: http://httpbin.org predicates: - Path=/foo/** filters: - RewritePath=/foo/(?<segment>.*), /$\{segment}
全部的/foo/**開始的路徑都會命中配置的router。
請求http://httpbin.org/foo/get ,會轉到http://httpbin.org/get,結果以下:
{ "args": { }, "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9,zh-TW;q=0.8", "Cache-Control": "max-age=0", "Cookie": "Hm_lvt_0c0e9d9b1e7d617b3e6842e85b9fb068=1550127915; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%22168eada0ded53b-0d5d8c3ba9b7a2-10316653-1296000-168eada0dee3ad%22%2C%22%24device_id%22%3A%22168eada0ded53b-0d5d8c3ba9b7a2-10316653-1296000-168eada0dee3ad%22%2C%22props%22%3A%7B%7D%7D; _gauges_unique_month=1; _gauges_unique_year=1; _gauges_unique=1", "Forwarded": "proto=http;host=\"127.0.0.1:8080\";for=\"127.0.0.1:62278\"", "Host": "httpbin.org", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36", "X-Forwarded-Host": "127.0.0.1:8080", "X-Forwarded-Prefix": "/foo" }, "origin": "127.0.0.1, 124.74.78.150, 127.0.0.1", "url": "https://127.0.0.1:8080/get" }
Spring Cloud Gateway內置了的過濾器工廠,足夠是大部分場景使用,並且咱們能夠實現GatewayFilter和Ordered 這兩個接口來自定義過濾器。代碼以下:
/** * 統計某個或者某種路由的處理時長 */ public class CustomerGatewayFilter implements GatewayFilter, Ordered { private static final Logger log = LoggerFactory.getLogger( CustomerGatewayFilter.class ); private static final String COUNT_START_TIME = "countStartTime"; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { exchange.getAttributes().put(COUNT_START_TIME, Instant.now().toEpochMilli() ); return chain.filter(exchange).then( Mono.fromRunnable(() -> { long startTime = exchange.getAttribute(COUNT_START_TIME); long endTime=(Instant.now().toEpochMilli() - startTime); log.info(exchange.getRequest().getURI().getRawPath() + ": " + endTime + "ms"); }) ); } @Override public int getOrder() { return 0; } }
上述代碼中,getOrder()方法是來給過濾器設定優先級別的,值越大則優先級越低。須要將自定義的GatewayFilter 註冊到router中,代碼以下:
@Bean public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route(r -> r.path("/customer/**") .filters(f -> f.filter(new CustomerGatewayFilter()) .addResponseHeader("X-Response-test", "test")) .uri("http://httpbin.org:80/get") .id("customer_filter_router") ) .build(); }
使用 curl 測試,命令行輸入:
curl http://localhost:8080/customer/555
控制檯輸出以下:
2019-02-22 10:39:47.840 INFO 1310 --- [ctor-http-nio-3] com.gf.config.CustomerGatewayFilter : /customer/555: 314ms
自定義GatewayFilter又有兩種實現方式,一種是上面的直接 實現GatewayFilter接口,另外一種是 自定義過濾器工廠(繼承AbstractGatewayFilterFactory類) , 選擇自定義過濾器工廠的方式,能夠在配置文件中配置過濾器了。
@Component public class CustomerGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomerGatewayFilterFactory.Config> { private static final Logger log = LoggerFactory.getLogger( CustomerGatewayFilterFactory.class ); private static final String COUNT_START_TIME = "countStartTime"; @Override public List<String> shortcutFieldOrder() { return Arrays.asList("enabled"); } public CustomerGatewayFilterFactory() { super(Config.class); log.info("Loaded GatewayFilterFactory [CustomerGatewayFilterFactory]"); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { if (!config.isEnabled()) { return chain.filter(exchange); } exchange.getAttributes().put(COUNT_START_TIME, System.currentTimeMillis()); return chain.filter(exchange).then( Mono.fromRunnable(() -> { Long startTime = exchange.getAttribute(COUNT_START_TIME); if (startTime != null) { StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath()) .append(": ") .append(System.currentTimeMillis() - startTime) .append("ms"); sb.append(" params:").append(exchange.getRequest().getQueryParams()); log.info(sb.toString()); } }) ); }; } public static class Config { /** * 控制是否開啓統計 */ private boolean enabled; public Config() {} public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } } }
application.yml 中 網關路由配置以下:
spring: profiles: elapse_route cloud: gateway: routes: - id: elapse_route uri: http://httpbin.org:80/get filters: - Customer=true predicates: - Method=GET
使用 curl 測試,命令行輸入:
curl http://localhost:8080/customer?foo=1
控制檯輸出以下:
2019-02-23 13:37:08.769 INFO 1700 --- [ctor-http-nio-3] c.g.config.CustomerGatewayFilterFactory : /customer: 585ms params:{foo=[1]}
Spring Cloud Gateway框架內置的GlobalFilter以下:
內置的 GlobalFilter 可以知足大多數的需求了,可是若是遇到特殊狀況,內置知足不了咱們的需求,還能夠自定義GlobalFilter。
下面的咱們自定義一個GlobalFilter,去校驗全部請求的請求參數中是否包含「token」,如何不包含請求參數「token」則不轉發路由,不然執行正常的邏輯。
/** * Token 校驗全局過濾器 */ @Component public class AuthorizeFilter implements GlobalFilter, Ordered { private static final Logger log = LoggerFactory.getLogger( AuthorizeFilter.class ); private static final String AUTHORIZE_TOKEN = "token"; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getQueryParams().getFirst( AUTHORIZE_TOKEN ); if ( StringUtils.isBlank( token )) { log.info( "token is empty ..." ); exchange.getResponse().setStatusCode( HttpStatus.UNAUTHORIZED ); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return 0; } }
使用 curl 測試,命令行輸入:
curl http://localhost:8080/customer?foo=1
控制檯輸出以下:
token is empty ...
源碼 : https://github.com/gf-huanchupk/SpringCloudLearning/tree/master/chapter13/springcloud-gateway-filter
歡迎關注個人公衆號《程序員果果》,關注有驚喜~~