SpringCloud系列:spring cloud gateway之filter篇

      網友投稿 1146 2025-04-06

      在上一篇文章詳細的介紹了Gateway的Predict,Predict決定了請求由哪一個路由處理,在路由處理之前,需要經過“pre”類型的過濾器處理,處理返回響應之后,可以由“post”類型的過濾器處理。

      filter的作用和生命周期

      由filter工作流程點,可以知道filter有著非常重要的作用,在“pre”類型的過濾器可以做參數校驗、權限校驗、流量監控、日志輸出、協議轉換等,在“post”類型的過濾器中可以做響應內容、響應頭的修改,日志的輸出,流量監控等。首先需要弄清一點為什么需要網關這一層,這就不得不說下filter的作用了。

      作用

      當我們有很多個服務時,比如下圖中的user-service、goods-service、sales-service等服務,客戶端請求各個服務的Api時,每個服務都需要做相同的事情,比如鑒權、限流、日志輸出等。

      對于這樣重復的工作,有沒有辦法做的更好,答案是肯定的。在微服務的上一層加一個全局的權限控制、限流、日志輸出的Api Gatewat服務,然后再將請求轉發到具體的業務服務層。這個Api Gateway服務就是起到一個服務邊界的作用,外接的請求訪問系統,必須先通過網關層。

      生命周期

      Spring Cloud Gateway同zuul類似,有“pre”和“post”兩種方式的filter。客戶端的請求先經過“pre”類型的filter,然后將請求轉發到具體的業務服務,比如上圖中的user-service,收到業務服務的響應之后,再經過“post”類型的filter處理,最后返回響應到客戶端。

      與zuul不同的是,filter除了分為“pre”和“post”兩種方式的filter外,在Spring Cloud Gateway中,filter從作用范圍可分為另外兩種,一種是針對于單個路由的gateway filter,它在配置文件中的寫法同predict類似;另外一種是針對于所有路由的global gateway filer。現在從作用范圍劃分的維度來講解這兩種filter。

      gateway filter

      過濾器允許以某種方式修改傳入的HTTP請求或傳出的HTTP響應。過濾器可以限定作用在某些特定請求路徑上。Spring Cloud Gateway包含許多內置的GatewayFilter工廠。

      GatewayFilter工廠同上一篇介紹的Predicate工廠類似,都是在配置文件Application.yml中配置,遵循了約定大于配置的思想,只需要在配置文件配置GatewayFilter Factory的名稱,而不需要寫全部的類名,比如AddRequestHeaderGatewayFilterFactory只需要在配置文件中寫AddRequestHeader,而不是全部類名。在配置文件中配置的GatewayFilter Factory最終都會相應的過濾器工廠類處理。

      Spring Cloud Gateway 內置的過濾器工廠一覽表如下:

      現在挑幾個常見的過濾器工廠來講解,每一個過濾器工廠在官方文檔都給出了詳細的使用案例,如果不清楚的還可以在org.springframework.cloud.gateway.filter.factory看每一個過濾器工廠的源碼。

      AddRequestHeader GatewayFilter Factory

      創建工程,引入相關的依賴,包括spring boot 版本2.0.5,spring Cloud版本Finchley,gateway依賴如下:

      1.??

      2.???? ?org.springframework.cloud

      3.???? ?spring-cloud-starter-gateway

      4.??

      在工程的配置文件中,加入以下的配置:

      1.??server:

      2.????port:8081

      3.??spring:

      4.????profiles:

      5.???? ?active:add_request_header_route

      6.

      7.??---

      8.??spring:

      9.????cloud:

      10.???? ?gateway:

      11.???? ? ?routes:

      12.???? ? ?-id:add_request_header_route

      13.???? ? ? ?uri:http://httpbin.org:80/get

      14.???? ? ? ?filters:

      15.???? ? ? ?-AddRequestHeader=X-Request-Foo,Bar

      16.???? ? ? ?predicates:

      17.???? ? ? ?-After=2017-01-20T17:42:47.789-07:00[America/Denver]

      18.????profiles:add_request_header_route

      在上述的配置中,工程的啟動端口為8081,配置文件為addrequestheaderroute,在addrequestheaderroute配置中,配置了roter的id為addrequestheader_route,路由地址為http://httpbin.org:80/get,該router有AfterPredictFactory,有一個filter為AddRequestHeaderGatewayFilterFactory(約定寫成AddRequestHeader),AddRequestHeader過濾器工廠會在請求頭加上一對請求頭,名稱為X-Request-Foo,值為Bar。為了驗證AddRequestHeaderGatewayFilterFactory是怎么樣工作的,查看它的源碼,AddRequestHeaderGatewayFilterFactory的源碼如下:

      1.??publicclassAddRequestHeaderGatewayFilterFactoryextendsAbstractNameValueGatewayFilterFactory{

      2.

      3.???? ?@Override

      4.???? ?publicGatewayFilterapply(NameValueConfigconfig){

      5.???? ? ? ?return(exchange,chain)->{

      6.???? ? ? ? ? ?ServerHttpRequestrequest?=exchange.getRequest().mutate()

      7.???? ? ? ? ? ? ? ? ? ?.header(config.getName(),config.getValue())

      8.???? ? ? ? ? ? ? ? ? ?.build();

      9.

      10.???? ? ? ? ? ?returnchain.filter(exchange.mutate().request(request).build());

      11.???? ? ? ?};

      12.???? ?}

      13.

      14.??}

      由上面的代碼可知,根據舊的ServerHttpRequest創建新的ServerHttpRequest ,在新的ServerHttpRequest加了一個請求頭,然后創建新的ServerWebExchange ,提交過濾器鏈繼續過濾。

      啟動工程,通過curl命令來模擬請求:

      1.??curl localhost:8081

      最終顯示了從 http://httpbin.org:80/get得到了請求,響應如下:

      1.??{

      2.????"args":{},

      3.????"headers":{

      4.???? ?"Accept":"*/*",

      5.???? ?"Connection":"close",

      6.???? ?"Forwarded":"proto=http;host=\"localhost:8081\";for=\"0:0:0:0:0:0:0:1:56248\"",

      7.???? ?"Host":"httpbin.org",

      8.???? ?"User-Agent":"curl/7.58.0",

      9.???? ?"X-Forwarded-Host":"localhost:8081",

      10.???? ?"X-Request-Foo":"Bar"

      SpringCloud系列:spring cloud gateway之filter篇

      11.????},

      12.????"origin":"0:0:0:0:0:0:0:1, 210.22.21.66",

      13.????"url":"http://localhost:8081/get"

      14.??}

      可以上面的響應可知,確實在請求頭中加入了X-Request-Foo這樣的一個請求頭,在配置文件中配置的AddRequestHeader過濾器工廠生效。

      跟AddRequestHeader過濾器工廠類似的還有AddResponseHeader過濾器工廠,在此就不再重復。

      RewritePath GatewayFilter Factory

      在Nginx服務啟中有一個非常強大的功能就是重寫路徑,Spring Cloud Gateway默認也提供了這樣的功能,這個功能是Zuul沒有的。在配置文件中加上以下的配置:

      1.??spring:

      2.????profiles:

      3.???? ?active:rewritepath_route

      4.??---

      5.??spring:

      6.????cloud:

      7.???? ?gateway:

      8.???? ? ?routes:

      9.???? ? ?-id:rewritepath_route

      10.???? ? ? ?uri:https://blog.csdn.net

      11.???? ? ? ?predicates:

      12.???? ? ? ?-Path=/foo/**

      13.???? ? ? ?filters:

      14.???? ? ? ?-RewritePath=/foo/(?.*),/$\{segment}

      15.????profiles:rewritepath_route

      上面的配置中,所有的/foo/*開始的路徑都會命中配置的router,并執行過濾器的邏輯,在本案例中配置了RewritePath過濾器工廠,此工廠將/foo/(?.)重寫為{segment},然后轉發到https://blog.csdn.net。比如在網頁上請求localhost:8081/foo/forezp,此時會將請求轉發到https://blog.csdn.net/forezp的頁面,比如在網頁上請求localhost:8081/foo/forezp/1,頁面顯示404,就是因為不存在https://blog.csdn.net/forezp/1這個頁面。

      自定義過濾器

      Spring Cloud Gateway內置了19種強大的過濾器工廠,能夠滿足很多場景的需求,那么能不能自定義自己的過濾器呢,當然是可以的。在spring Cloud Gateway中,過濾器需要實現GatewayFilter和Ordered2個接口。寫一個RequestTimeFilter,代碼如下:

      1.??publicclassRequestTimeFilterimplementsGatewayFilter,Ordered{

      2.

      3.???? ?privatestaticfinalLoglog?=LogFactory.getLog(GatewayFilter.class);

      4.???? ?privatestaticfinalStringREQUEST_TIME_BEGIN?="requestTimeBegin";

      5.

      6.???? ?@Override

      7.???? ?publicMonofilter(ServerWebExchangeexchange,GatewayFilterChainchain){

      8.

      9.???? ? ? ?exchange.getAttributes().put(REQUEST_TIME_BEGIN,System.currentTimeMillis());

      10.???? ? ? ?returnchain.filter(exchange).then(

      11.???? ? ? ? ? ? ? ?Mono.fromRunnable(()->{

      12.???? ? ? ? ? ? ? ? ? ?LongstartTime?=exchange.getAttribute(REQUEST_TIME_BEGIN);

      13.???? ? ? ? ? ? ? ? ? ?if(startTime?!=null){

      14.???? ? ? ? ? ? ? ? ? ? ? ?log.info(exchange.getRequest().getURI().getRawPath()+": "+(System.currentTimeMillis()-startTime)+"ms");

      15.???? ? ? ? ? ? ? ? ? ?}

      16.???? ? ? ? ? ? ? ?})

      17.???? ? ? ?);

      18.

      19.???? ?}

      20.

      21.???? ?@Override

      22.???? ?publicintgetOrder(){

      23.???? ? ? ?return0;

      24.???? ?}

      25.??}

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

      1.???? ?@Bean

      2.???? ?publicRouteLocatorcustomerRouteLocator(RouteLocatorBuilderbuilder){

      3.???? ? ? ?// @formatter:off

      4.???? ? ? ?returnbuilder.routes()

      5.???? ? ? ? ? ? ? ?.route(r?->r.path("/customer/**")

      6.???? ? ? ? ? ? ? ? ? ? ? ?.filters(f?->f.filter(newRequestTimeFilter())

      7.???? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?.addResponseHeader("X-Response-Default-Foo","Default-Bar"))

      8.???? ? ? ? ? ? ? ? ? ? ? ?.uri("http://httpbin.org:80/get")

      9.???? ? ? ? ? ? ? ? ? ? ? ?.order(0)

      10.???? ? ? ? ? ? ? ? ? ? ? ?.id("customer_filter_router")

      11.???? ? ? ? ? ? ? ?)

      12.???? ? ? ? ? ? ? ?.build();

      13.???? ? ? ?// @formatter:on

      14.???? ?}

      重啟程序,通過curl命令模擬請求:

      1.???curl localhost:8081/customer/123

      在程序的控制臺輸出一下的請求信息的日志:

      1.??2018-11-1615:02:20.177?INFO?20488---[ctor-http-nio-3]o.s.cloud.gateway.filter.GatewayFilter??:/customer/123:152ms

      自定義過濾器工廠

      在上面的自定義過濾器中,有沒有辦法自定義過濾器工廠類呢?這樣就可以在配置文件中配置過濾器了。現在需要實現一個過濾器工廠,在打印時間的時候,可以設置參數來決定是否打印請參數。查看GatewayFilterFactory的源碼,可以發現GatewayFilterfactory的層級如下:

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

      過濾器工廠的頂級接口是GatewayFilterFactory,有2個兩個較接近具體實現的抽象類,分別為AbstractGatewayFilterFactory和AbstractNameValueGatewayFilterFactory,這2個類前者接收一個參數,比如它的實現類RedirectToGatewayFilterFactory;后者接收2個參數,比如它的實現類AddRequestHeaderGatewayFilterFactory類。現在需要將請求的日志打印出來,需要使用一個參數,這時可以參照RedirectToGatewayFilterFactory的寫法。

      1.??publicclassRequestTimeGatewayFilterFactoryextendsAbstractGatewayFilterFactory{

      2.

      3.

      4.???? ?privatestaticfinalLoglog?=LogFactory.getLog(GatewayFilter.class);

      5.???? ?privatestaticfinalStringREQUEST_TIME_BEGIN?="requestTimeBegin";

      6.???? ?privatestaticfinalStringKEY?="withParams";

      7.

      8.???? ?@Override

      9.???? ?publicListshortcutFieldOrder(){

      10.???? ? ? ?returnArrays.asList(KEY);

      11.???? ?}

      12.

      13.???? ?publicRequestTimeGatewayFilterFactory(){

      14.???? ? ? ?super(Config.class);

      15.???? ?}

      16.

      17.???? ?@Override

      18.???? ?publicGatewayFilterapply(Configconfig){

      19.???? ? ? ?return(exchange,chain)->{

      20.???? ? ? ? ? ?exchange.getAttributes().put(REQUEST_TIME_BEGIN,System.currentTimeMillis());

      21.???? ? ? ? ? ?returnchain.filter(exchange).then(

      22.???? ? ? ? ? ? ? ? ? ?Mono.fromRunnable(()->{

      23.???? ? ? ? ? ? ? ? ? ? ? ?LongstartTime?=exchange.getAttribute(REQUEST_TIME_BEGIN);

      24.???? ? ? ? ? ? ? ? ? ? ? ?if(startTime?!=null){

      25.???? ? ? ? ? ? ? ? ? ? ? ? ? ?StringBuildersb?=newStringBuilder(exchange.getRequest().getURI().getRawPath())

      26.???? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?.append(": ")

      27.???? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?.append(System.currentTimeMillis()-startTime)

      28.???? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?.append("ms");

      29.???? ? ? ? ? ? ? ? ? ? ? ? ? ?if(config.isWithParams()){

      30.???? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?sb.append(" params:").append(exchange.getRequest().getQueryParams());

      31.???? ? ? ? ? ? ? ? ? ? ? ? ? ?}

      32.???? ? ? ? ? ? ? ? ? ? ? ? ? ?log.info(sb.toString());

      33.???? ? ? ? ? ? ? ? ? ? ? ?}

      34.???? ? ? ? ? ? ? ? ? ?})

      35.???? ? ? ? ? ?);

      36.???? ? ? ?};

      37.???? ?}

      38.

      39.

      40.???? ?publicstaticclassConfig{

      41.

      42.???? ? ? ?privatebooleanwithParams;

      43.

      44.???? ? ? ?publicbooleanisWithParams(){

      45.???? ? ? ? ? ?returnwithParams;

      46.???? ? ? ?}

      47.

      48.???? ? ? ?publicvoidsetWithParams(booleanwithParams){

      49.???? ? ? ? ? ?this.withParams=withParams;

      50.???? ? ? ?}

      51.

      52.???? ?}

      53.??}

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

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

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

      1.???? ?@Bean

      2.???? ?publicRequestTimeGatewayFilterFactoryelapsedGatewayFilterFactory(){

      3.???? ? ? ?returnnewRequestTimeGatewayFilterFactory();

      4.???? ?}

      然后可以在配置文件中配置如下:

      1.??spring:

      2.????profiles:

      3.???? ?active:elapse_route

      4.

      5.??---

      6.??spring:

      7.????cloud:

      8.???? ?gateway:

      9.???? ? ?routes:

      10.???? ? ?-id:elapse_route

      11.???? ? ? ?uri:http://httpbin.org:80/get

      12.???? ? ? ?filters:

      13.???? ? ? ?-RequestTime=false

      14.???? ? ? ?predicates:

      15.???? ? ? ?-After=2017-01-20T17:42:47.789-07:00[America/Denver]

      16.????profiles:elapse_route

      啟動工程,在瀏覽器***問localhost:8081?name=forezp,可以在控制臺上看到,日志輸出了請求消耗的時間和請求參數。

      global filter

      Spring Cloud Gateway根據作用范圍劃分為GatewayFilter和GlobalFilter,二者區別如下:

      GatewayFilter : 需要通過spring.cloud.routes.filters 配置在具體路由下,只作用在當前路由上或通過spring.cloud.default-filters配置在全局,作用在所有路由上

      §

      GlobalFilter : 全局過濾器,不需要在配置文件中配置,作用在所有的路由上,最終通過GatewayFilterAdapter包裝成GatewayFilterChain可識別的過濾器,它為請求業務以及路由的URI轉換為真實業務服務的請求地址的核心過濾器,不需要配置,系統初始化時加載,并作用在每個路由上。

      Spring Cloud Gateway框架內置的GlobalFilter如下:

      上圖中每一個GlobalFilter都作用在每一個router上,能夠滿足大多數的需求。但是如果遇到業務上的定制,可能需要編寫滿足自己需求的GlobalFilter。在下面的案例中將講述如何編寫自己GlobalFilter,該GlobalFilter會校驗請求中是否包含了請求參數“token”,如何不包含請求參數“token”則不轉發路由,否則執行正常的邏輯。代碼如下:

      1.??publicclassTokenFilterimplementsGlobalFilter,Ordered{

      2.

      3.???? ?Loggerlogger=LoggerFactory.getLogger(TokenFilter.class);

      4.???? ?@Override

      5.???? ?publicMonofilter(ServerWebExchangeexchange,GatewayFilterChainchain){

      6.???? ? ? ?Stringtoken?=exchange.getRequest().getQueryParams().getFirst("token");

      7.???? ? ? ?if(token?==null||token.isEmpty()){

      8.???? ? ? ? ? ?logger.info("token is empty...");

      9.???? ? ? ? ? ?exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);

      10.???? ? ? ? ? ?returnexchange.getResponse().setComplete();

      11.???? ? ? ?}

      12.???? ? ? ?returnchain.filter(exchange);

      13.???? ?}

      14.

      15.???? ?@Override

      16.???? ?publicintgetOrder(){

      17.???? ? ? ?return-100;

      18.???? ?}

      19.??}

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

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

      1.??@Bean

      2.??publicTokenFiltertokenFilter(){

      3.???? ? ? ?returnnewTokenFilter();

      4.??}

      啟動工程,使用curl命令請求:

      1.???curl localhost:8081/customer/123

      可以看到請沒有被轉發,請求被終止,并在控制臺打印了如下日志:

      1.??2018-11-1615:30:13.543?INFO?19372---[ctor-http-nio-2]gateway.TokenFilter? ? ? ? ? ? ? ? ? ? ?:token?isempty...

      上面的日志顯示了請求進入了沒有傳“token”的邏輯。

      總結

      本篇文章講述了Spring Cloud Gateway中的過濾器,包括GatewayFilter和GlobalFilter。從官方文檔的內置過濾器講起,然后講解自定義GatewayFilter、GatewayFilterFactory以及自定義的GlobalFilter。有很多內置的過濾器并沒有講述到,比如限流過濾器,這個我覺得是比較重要和大家關注的過濾器,將在之后的文章講述。

      參考資料

      https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.0.M1/single/spring-cloud-gateway.html

      https://www.jianshu.com/p/eb3a67291050

      https://blog.csdn.net/qq_36236890/article/details/80822051

      https://windmt.com/2018/05/08/spring-cloud-14-spring-cloud-gateway-filter

      源碼下載

      https://github.com/forezp/SpringCloudLearning/tree/master/sc-f-gateway-predicate

      方志朋簡介:SpringCloud中國社區聯合創始人,博客訪問量突破一千萬,愛好開源,熱愛分享,活躍于各大社區,保持著非常強的學習驅動力,終身學習踐行者,終身學習受益者。目前就職于國內某家知名互聯網保險公司,擔任DEVOPS工程師,對微服務領域和持續集成領域研究較深,精通微服務框架SpringCloud

      Spring Spring Cloud

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:excel如何使用IF語句
      下一篇:Excel怎么設置下拉表格(excel表格中如何設置下拉列表)
      相關文章
      亚洲日韩乱码中文无码蜜桃臀网站| 久久久久se色偷偷亚洲精品av| 亚洲va精品中文字幕| 亚洲国产一成久久精品国产成人综合| 亚洲一久久久久久久久| 亚洲小说区图片区| 亚洲av午夜福利精品一区| 亚洲国产精品无码av| 亚洲精品夜夜夜妓女网| 亚洲精品一品区二品区三品区| 精品亚洲综合在线第一区| 久久99国产亚洲高清观看首页| 国产亚洲人成无码网在线观看 | 2020久久精品亚洲热综合一本| 亚洲国产成人久久精品app| 亚洲国产精品xo在线观看| 亚洲不卡1卡2卡三卡2021麻豆| 国产精品亚洲自在线播放页码| 亚洲高清视频在线| 亚洲国产精品网站在线播放| 色欲色欲天天天www亚洲伊| 亚洲6080yy久久无码产自国产| 国产成人不卡亚洲精品91| 亚洲美女在线国产| 亚洲综合AV在线在线播放| 久久夜色精品国产亚洲| 久久亚洲精品中文字幕| 亚洲成年人免费网站| 国内精品久久久久影院亚洲 | 亚洲色大成网站www久久九| 亚洲av无码一区二区三区天堂| 亚洲?V无码乱码国产精品| 久久亚洲国产精品123区| 亚洲精品无码久久一线| 亚洲天天在线日亚洲洲精| 亚洲六月丁香六月婷婷色伊人| 亚洲日韩精品无码专区加勒比☆| 国产亚洲漂亮白嫩美女在线| 亚洲综合国产一区二区三区| 久久亚洲美女精品国产精品| 国产人成亚洲第一网站在线播放|