本文采用Spring cloud本文爲2.1.8RELEASE,version=Greenwich.SR3 java
本文基於前兩篇文章eureka-server、eureka-client、eureka-ribbon、eureka-feign和spring-gataway的實現。 參考react
Spring Cloud Gateway內部已經提供很是多的過濾器filter,Hystrix Gateway Filter、Prefix PathGateway Filter等。感興趣的小夥伴能夠直接閱讀Spring Cloud Gateway官網相關文檔或直接閱讀源碼。可是不少狀況下自帶的過濾器並不能知足咱們的需求,因此自定義過濾器就顯得的很是重要。本文主要介紹全局過濾器(Global Filter)和局部過濾器(Gateway Filter)。git
自定義過濾器須要實現GatewayFilter和Ordered。其中GatewayFilter主要是用來實現自定義的具體邏輯,Ordered中的getOrder()方法是來給過濾器設定優先級別的,值越大優先級別越低。github
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;
}
}
複製代碼
咱們在請求過來到達的時候,往ServerWebExchange中放了一個屬性TIME,屬性的值爲當前時間的毫秒數,而後請求結束後,又會將請求的時間數取出來來和當前時間數作差,獲得耗時時間數。web
如何區分「pre」和「post」?spring
- pre就是chain.filter(exchange)部分.
- post就是then()部分.
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();
}
}
複製代碼
啓動eureka-server、eureka-client、eureka-ribbon、spring-gateway相關服務,訪問http://localhost:8100/ribbon/sayHello地址,頁面顯示結果以下:apache
這時我打開控制檯能夠看到日誌輸出爲: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;
}
}
複製代碼
將MyGlobalFilter添加到Bean中app
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;
}
}
複製代碼
這裏只是簡單模擬,若是感興趣的小夥伴能夠本身嘗試將全部參數取出來並解析(能夠利用反射來實現)。分佈式
重啓動服務,訪問http://localhost:8100/ribbon/sayHello,顯示以下: ide
我能夠看到顯示訪問是不生效的,咱們在請求中加入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
複製代碼
引用官網原文: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). 說明GlobalFilter在將來的版本中會又一些變化。
至此,自定義filter的兩種方式就簡單的實現完成了。一樣的方式能夠在feign作測試。
在前一篇文章中,配置文件中有這樣一段配置:
filters:
- StripPrefix=1
- AddResponseHeader=X-Response-Default-Foo, Default-Bar
複製代碼
StripPrefix和AddResponseHeader這兩個配置其實是兩個Filter的過濾器工廠(GatewayFilterFactory),接下來將介紹過濾器工廠的使用,相對來講這種方式更加靈活。
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類傳給父類,不然會報錯。
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();
}
}
複製代碼
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 主要增長了這個配置。
訪問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有了簡單的瞭解。