Gateway服務(wù)網(wǎng)關(guān)之過濾器
文章已收錄到我的Github精選,歡迎Star:https://github.com/yehongzhi/learningSummary
寫在前面
前一篇文章寫了Gateway的Predicate(用于路由轉(zhuǎn)發(fā)),那么這篇文章就介紹另一個主要的核心,那就是Filter(過濾器)。
過濾器有什么作用呢?工作流程是怎么樣的呢?請看下圖:

從圖中很明顯可以看出,在請求后端服務(wù)前后都需要經(jīng)過Filter,于是乎Filter的作用就明確了,在PreFilter(請求前處理)可以做參數(shù)校驗、流量監(jiān)控、日志記錄、修改請求內(nèi)容等等,在PostFilter(請求后處理)可以做響應(yīng)內(nèi)容修改。
過濾器
Filter分為局部和全局兩種:
局部Filter(GatewayFilter的子類)是作用于單個路由。如果需要使用全局路由,需要配置Default Filters。 全局Filter(GlobalFilter的子類),不需要配置路由,系統(tǒng)初始化作用到所有路由上。
局部過濾器
SpringCloud Gateway內(nèi)置了很多路由過濾器,他們都是由GatewayFilter的工廠類產(chǎn)生。
AddRequestParameter GatewayFilter
該過濾器可以給請求添加參數(shù)。
比如我在consumer服務(wù)有一個帶有userName參數(shù)的接口,我想請求網(wǎng)關(guān)路由轉(zhuǎn)發(fā)的時候給加上一個userName=yehongzhi的參數(shù)。
@RequestMapping(value?=?"/getOrder",method?=?RequestMethod.GET)
public?String?getOrder(@RequestParam(name?=?"userName")?String?userName)?{
????return?"獲取到傳入的用戶名稱:"?+?userName;
}
配置如下:
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?add_request_parameter
??????????uri:?http://localhost:8081/getOrder
??????????predicates:
????????????-?Method=GET
????????????-?Path=/getOrder
??????????filters:
????????????-?AddRequestParameter=userName,yehongzhi
那么當我請求網(wǎng)關(guān)時,輸入http://localhost:9201/getOrder,我們能看到默認加上的userName。

StripPrefix GatewayFilter
該過濾器可以去除指定數(shù)量的路徑前綴。
比如我想把請求網(wǎng)關(guān)的路徑前綴的第一級去掉,就可以這樣配置實現(xiàn):
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?strip_prefix_gateway
??????????uri:?http://localhost:8081
??????????predicates:
????????????-?Path=/consumer/**
??????????filters:
????????????-?StripPrefix=1
當請求路徑http://localhost:9201/consumer/getDetail/1,能獲得結(jié)果。

相當于請求http://localhost:8081/getDetail/1,結(jié)果是一樣的。

PrefixPath GatewayFilter
該過濾器與上一個過濾器相反,是給原有的路徑加上指定的前綴。
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?prefix_path_gateway
??????????uri:?http://localhost:8081
??????????predicates:
????????????-?Path=/getUserInfo/**
??????????filters:
????????????-?PrefixPath=/consumer
當請求http://localhost:9201/getUserInfo/1時,跟請求http://localhost:8081/consumer/getUserInfo/1是一樣的。


Hystrix GatewayFilter
網(wǎng)關(guān)當然有熔斷機制,所以該過濾器集成了Hystrix,實現(xiàn)了熔斷的功能。怎么使用呢?首先需要引入Hystrix的maven依賴。
<dependency>
????<groupId>org.springframework.cloudgroupId>
????<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
在gateway服務(wù)添加fallback()方法
@RestController
public?class?FallBackController?{
????@RequestMapping("/fallback")
????public?String?fallback(){
????????return?"系統(tǒng)繁忙,請稍后再試!";
????}
}
在網(wǎng)關(guān)服務(wù)的配置如下,對GET請求方式的請求路由轉(zhuǎn)發(fā)出錯時,會觸發(fā)服務(wù)降級:
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?hystrix_filter
??????????uri:?http://localhost:8081
??????????predicates:
????????????-?Method=GET
??????????filters:
????????????-?name:?Hystrix
??????????????args:
????????????????name:?fallbackcmd
????????????????fallbackUri:?forward:/fallback
這時我們把8081的服務(wù)停止,讓網(wǎng)關(guān)請求不到對應(yīng)的服務(wù),從而觸發(fā)服務(wù)降級。

RequestRateLimiter GatewayFilter
該過濾器可以提供限流的功能,使用RateLimiter實現(xiàn)來確定是否允許當前請求繼續(xù)進行,如果請求太大默認會返回HTTP 429-太多請求狀態(tài)。怎么用呢?首先還是得引入Maven依賴。
<dependency>
????<groupId>org.springframework.bootgroupId>
????<artifactId>spring-boot-starter-data-redis-reactiveartifactId>
dependency>
接著增加個配置類。
@Configuration
public?class?LimiterConfig?{
????/**
?????*?ip限流器
?????*/
????@Bean
????public?KeyResolver?ipKeyResolver()?{
????????return?exchange?->?Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
????}
}
然后配置如下,對GET方式的請求增加限流策略:
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?hystrix_filter
??????????uri:?http://localhost:8081
??????????predicates:
????????????-?Method=GET
??????????filters:
????????????-?name:?RequestRateLimiter
??????????????args:
????????????????redis-rate-limiter.replenishRate:?1?#每秒允許處理的請求數(shù)量
????????????????redis-rate-limiter.burstCapacity:?2?#每秒最大處理的請求數(shù)量
????????????????key-resolver:?"#{@ipKeyResolver}"?#限流策略,對應(yīng)策略的Bean
??redis:
????port:?6379
????host:?192.168.1.5?#redis地址
然后啟動服務(wù),連續(xù)請求地址http://localhost:9201/getDetail/1,就會觸發(fā)限流,報429錯誤。

因為內(nèi)置的過濾器實在是太多了,這里就不一一列舉了,有興趣的同學(xué)可以到官網(wǎng)自行學(xué)習(xí)。
自定義局部過濾器
如果內(nèi)置的局部過濾器不能滿足需求,那么我們就得使用自定義過濾器,怎么用呢?下面用一個例子,我們自定義一個白名單的過濾器,userName在白名單內(nèi)的才可以訪問,不在白名單內(nèi)的就返回401錯誤碼(Unauthorized)。
局部過濾器需要實現(xiàn)GatewayFilter和Ordered接口,代碼如下:
public?class?WhiteListGatewayFilter?implements?GatewayFilter,?Ordered?{
?//白名單集合
????private?List?whiteList;
?//通過構(gòu)造器初始化白名單
????WhiteListGatewayFilter(List?whiteList)?{
????????this.whiteList?=?whiteList;
????}
????@Override
????public?Mono?filter(ServerWebExchange?exchange,?GatewayFilterChain?chain)? {
????????String?userName?=?exchange.getRequest().getQueryParams().getFirst("userName");
????????//白名單不為空,并且userName包含在白名單內(nèi),才可以訪問
????????if?(!CollectionUtils.isEmpty(whiteList)?&&?whiteList.contains(userName))?{
????????????return?chain.filter(exchange);
????????}
????????//如果白名單為空或者userName不在白名單內(nèi),則返回401
????????exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
????????return?exchange.getResponse().setComplete();
????}
????//優(yōu)先級,值越小優(yōu)先級越高
????@Override
????public?int?getOrder()?{
????????return?0;
????}
}
接著再定義一個過濾器工廠,注入到Spring容器中,代碼如下:
@Component
public?class?WhiteListGatewayFilterFactory?extends?AbstractConfigurable<WhiteListGatewayFilterFactory.Config>?implements?GatewayFilterFactory<WhiteListGatewayFilterFactory.Config>?{
????private?static?final?String?VALUE?=?"value";
????protected?WhiteListGatewayFilterFactory()?{
????????super(WhiteListGatewayFilterFactory.Config.class);
????}
????@Override
????public?List?shortcutFieldOrder()? {
????????return?Collections.singletonList(VALUE);
????}
????@Override
????public?GatewayFilter?apply(Config?config)?{
????????//獲取配置的白名單
????????String?whiteString?=?config.getValue();
????????List?whiteList?=?new?ArrayList<>(Arrays.asList(whiteString.split(",")));
????????//創(chuàng)建WhiteListGatewayFilter實例,返回
????????return?new?WhiteListGatewayFilter(whiteList);
????}
?//用于接收配置參數(shù)
????public?static?class?Config?{
????????private?String?value;
????????public?String?getValue()?{
????????????return?value;
????????}
????????public?void?setValue(String?value)?{
????????????this.value?=?value;
????????}
????}
}
最后,我們可以在application.yaml配置文件中加上配置使用:
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?white_list_filter
??????????uri:?http://localhost:8081
??????????predicates:
????????????-?Method=GET
??????????filters:
????????????-?WhiteList=yehongzhi?#等號后面配置的是白名單,用逗號隔開
接著啟動項目,先請求localhost:9201/getDetail/1,不帶userName,按預(yù)期會返回401,不能訪問。

請求帶有userName=yehongzhi的地址http://localhost:9201/getDetail/1?userName=yehongzhi,是在白名單內(nèi)的,所以能正常訪問。

全局過濾器
全局過濾器在系統(tǒng)初始化時就作用于所有的路由,不需要單獨去配置。全局過濾器的接口定義類是GlobalFilter,Gateway本身也有很多內(nèi)置的過濾器,我們打開類圖看看:

我們拿幾個比較有代表性的來做介紹,比如負載均衡的全局過濾器LoadBalancerClientFilter。
LoadBalancerClientFilter
該過濾器會解析到以lb://開頭的uri,比如這樣的配置:
spring:
??application:
????name:?api-gateway
??cloud:
????nacos:
??????discovery:
????????server-addr:?127.0.0.1:8848
????????service:?${spring.application.name}
????gateway:
??????routes:
????????-?id:?consumer
??????????uri:?lb://consumer?#使用lb協(xié)議,consumer是服務(wù)名,不再使用IP地址配置
??????????order:?1
??????????predicates:
????????????-?Path=/consumer/**?
這個全局過濾器就會取到consumer這個服務(wù)名,然后通過LoadBalancerClient獲取到ServiceInstance服務(wù)實例。根據(jù)獲取到的服務(wù)實例,重新組裝請求的url。
這就是一個全局過濾器應(yīng)用的例子,它是作用于全局,而且并不需要配置。下面我們探索一下自定義全局過濾器,假設(shè)需要統(tǒng)計用戶的IP地址訪問網(wǎng)關(guān)的總次數(shù),怎么做呢?
自定義全局過濾器
自定義全局過濾器需要實現(xiàn)GlobalFilter接口和Ordered接口。
@Component
public?class?IPAddressStatisticsFilter?implements?GlobalFilter,?Ordered?{
????@Override
????public?Mono?filter(ServerWebExchange?exchange,?GatewayFilterChain?chain)? {
????????InetSocketAddress?host?=?exchange.getRequest().getHeaders().getHost();
????????if?(host?==?null?||?host.getHostName()?==?null)?{
????????????exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
????????????return?exchange.getResponse().setComplete();
????????}
????????String?hostName?=?host.getHostName();
????????AtomicInteger?count?=?IpCache.CACHE.getOrDefault(hostName,?new?AtomicInteger(0));
????????count.incrementAndGet();
????????IpCache.CACHE.put(hostName,?count);
????????System.out.println("IP地址:"?+?hostName?+?",訪問次數(shù):"?+?count.intValue());
????????return?chain.filter(exchange);
????}
????@Override
????public?int?getOrder()?{
????????return?10101;
????}
}
//用于保存次數(shù)的緩存
public?class?IpCache?{
????public?static?final?Map?CACHE?=?new?ConcurrentHashMap<>();
}
啟動項目,然后請求服務(wù),可以看到控制臺打印結(jié)果。
IP地址:192.168.1.4,訪問次數(shù):1
IP地址:192.168.1.4,訪問次數(shù):2
IP地址:192.168.1.4,訪問次數(shù):3
IP地址:localhost,訪問次數(shù):1
IP地址:localhost,訪問次數(shù):2
IP地址:localhost,訪問次數(shù):3
IP地址:192.168.1.4,訪問次數(shù):4
總結(jié)
通過上一篇的Predicates和這篇的Filters基本上把服務(wù)網(wǎng)關(guān)的功能都實現(xiàn)了,包括路由轉(zhuǎn)發(fā)、權(quán)限攔截、流量統(tǒng)計、流量控制、服務(wù)熔斷、日志記錄等等。所以網(wǎng)關(guān)對于微服務(wù)架構(gòu)來說,網(wǎng)關(guān)服務(wù)是一個非常重要的部分,有很多一線的互聯(lián)網(wǎng)公司還會自研服務(wù)網(wǎng)關(guān)。因此掌握服務(wù)網(wǎng)關(guān)對于后端開發(fā)可以說是必備技能,感謝大家的閱讀。
覺得有用就點個贊吧,你的點贊是我創(chuàng)作的最大動力~
我是一個努力讓大家記住的程序員。我們下期再見?。?!
能力有限,如果有什么錯誤或者不當之處,請大家批評指正,一起學(xué)習(xí)交流!
