Spring Cloud Gateway實戰之五:內建filter

程式設計師欣宸發表於2021-11-18

歡迎訪問我的GitHub

https://github.com/zq2599/blog_demos

內容:所有原創文章分類彙總及配套原始碼,涉及Java、Docker、Kubernetes、DevOPS等;

本篇概覽

  • 作為《Spring Cloud Gateway實戰》系列的第五篇,是時候瞭解過濾器(filter)的作用了,本篇我們們一起來了解Spring Cloud Gateway內建好的過濾器,真是種類繁多功能強大

    AddRequestHeader

  • AddRequestHeader過濾器顧名思義,就是在請求頭部新增指定的內容
  • 帶有predicate的完整配置:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/hello/**
          filters:
            - AddRequestHeader=x-request-foo, bar-config
  • 帶有predicate的完整動態配置:
[
    {
        "id": "path_route_addr",
        "uri": "http://127.0.0.1:8082",
        "predicates": [
            {
                "name": "Path",
                "args": {
                    "pattern": "/hello/**"
                }
            }
        ],
        "filters": [
            {
                "name": "AddRequestHeader",
                "args": {
                    "name": "x-request-foo",
                    "value": "bar-dynamic"
                }
            }
        ]
    }
]
  • 實際效果:

在這裡插入圖片描述

AddRequestParameter

  • AddRequestParameter過濾器顧名思義,就是新增請求引數
  • 配置如下,服務提供方收到的請求中會多一個引數,名為foo,值為bar-config:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/hello/**
          filters:
            - AddRequestParameter=foo, bar-config
  • 帶有predicate的完整動態配置:
[
    {
        "id": "path_route_addr",
        "uri": "http://127.0.0.1:8082",
        "predicates": [
            {
                "name": "Path",
                "args": {
                    "pattern": "/hello/**"
                }
            }
        ],
        "filters": [
            {
                "name": "AddRequestParameter",
                "args": {
                    "name": "foo",
                    "value": "bar-dynamic"
                }
            }
        ]
    }
]
  • 實際效果:

在這裡插入圖片描述

AddResponseHeader

  • AddResponseHeader過濾器就是在響應的header中新增引數
  • 配置如下,客戶端收到的響應,其header中會多一個引數,名為foo,值為bar-config-response:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/hello/**
          filters:
          - AddResponseHeader=foo, bar-config-response
  • 帶有predicate的完整動態配置:
[
    {
        "id": "path_route_addr",
        "uri": "http://127.0.0.1:8082",
        "predicates": [
            {
                "name": "Path",
                "args": {
                    "pattern": "/hello/**"
                }
            }
        ],
        "filters": [
            {
                "name": "AddResponseHeader",
                "args": {
                    "name": "foo",
                    "value": "bar-dynamic-response"
                }
            }
        ]
    }
]
  • 實際效果:

在這裡插入圖片描述

DedupeResponseHeader

  • 服務提供方返回的response的header中,如果有的key出線了多個value(例如跨域場景下的Access-Control-Allow-Origin),DedupeResponseHeader過濾器可以將重複的value剔除調,剔除策略有三種:RETAIN_FIRST (保留第一個,預設), RETAIN_LAST(保留最後一個), RETAIN_UNIQUE(去重)
  • 配置如下,指定了兩個header key的去重,策略是保留最後一個:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/hello/**
          filters:
          - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_LAST

DedupeResponseHeader

  • 服務提供方返回的response的header中,如果有的key出線了多個value(例如跨域場景下的Access-Control-Allow-Origin),DedupeResponseHeader過濾器可以將重複的value剔除調,剔除策略有三種:RETAIN_FIRST (保留第一個,預設), RETAIN_LAST(保留最後一個), RETAIN_UNIQUE(去重)
  • 配置如下,指定了兩個header key的去重,策略是保留最後一個:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/hello/**
          filters:
          - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_LAST

CircuitBreaker

  • CircuitBreaker即斷路器,我們們在單獨的一篇中深入體驗這個強大的功能吧

FallbackHeaders

  • FallbackHeaders一般和CircuitBreaker配合使用,來看下面的配置,發生斷路後,請求會被轉發FallbackHeaders去處理,此時FallbackHeaders會在header中指定的key上新增異常資訊:
spring:
  cloud:
    gateway:
      routes:
      - id: ingredients
        uri: lb://ingredients
        predicates:
        - Path=//ingredients/**
        filters:
        - name: CircuitBreaker
          args:
            name: fetchIngredients
            fallbackUri: forward:/fallback
      - id: ingredients-fallback
        uri: http://localhost:9994
        predicates:
        - Path=/fallback
        filters:
        - name: FallbackHeaders
          args:
            executionExceptionTypeHeaderName: Test-Header

MapRequestHeader

  • MapRequestHeader用於header中的鍵值對複製,如下配置的意思是:如果請求header中有<font color="blue">Blue</font>就新增名為<font color="red">X-Request-Red</font>的key,其值和<font color="blue">Blue</font>的值一樣
  • 配置如下,指定了兩個header key的去重,策略是保留最後一個:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/hello/**
          filters:
          - MapRequestHeader=Blue, X-Request-Red
  • 如下圖,請求header中有Blue:

在這裡插入圖片描述

  • 再看服務提供方的日誌,顯示header中多了X-Request-Red:

在這裡插入圖片描述

  • 如果請求的header中已經存在<font color="blue">X-Request-Red</font>會出現什麼情況呢?如下圖,我們們把<font color="blue">X-Request-Red</font>寫在請求header中:

在這裡插入圖片描述

  • 在服務提供方打斷點,可以發現神奇的一幕,header中的所有key,對應的值其實都是集合,只是大多數情況下集合裡面只有一個元素,而MapRequestHeader新增的元素會被放入這個集合,不會影響原有內容:

在這裡插入圖片描述

PrefixPath

  • PrefixPath很好理解,就是轉發到服務提供者的時候,給path加字首
  • 例如我這邊服務提供者原始地址是<font color="blue">http://127.0.0.1:8082/hello/str</font>配置如下,如果我給閘道器配置PrefixPath=hello,那麼訪問閘道器的時候,請求路徑中就不需要<font color="blue">hello</font>了,配置如下:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/str
          filters:
          - PrefixPath=/hello
  • 如下圖,請求路徑無需<font color="blue">hello</font>:

在這裡插入圖片描述

PreserveHostHeader

  • PreserveHostHeader在轉發請求到服務提供者的時候,會保留host資訊(否則就只能由HTTP client來決定了)
  • 先看不使用PreserveHostHeader的效果,如下圖,服務提供者收到的請求header中的host就是閘道器配置的資訊:

在這裡插入圖片描述

  • 加上PreserveHostHeader試試,如下圖紅框,是真正的host資訊:

在這裡插入圖片描述

RequestRateLimiter

  • RequestRateLimiter用於限流,涉及內容較多,就放在單獨的章節深入研究吧

RedirectTo

  • RedirectTo的功能簡單直白:跳轉到指定位置,下面的配置中,uri欄位明顯是一個無效的地址,但請求還是會被RedirectTo轉發到指定位置去:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.1.1.1:11111
          predicates:
          - Path=/hello/**
          filters:
          - RedirectTo=302, http://127.0.0.1:8082/hello/str

RemoveRequestHeader

  • RemoveRequestHeader很好理解,刪除請求header中的指定值
  • 下面的配置會刪除請求header中的foo:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/hello/**
          filters:
          - RemoveRequestHeader=foo

RemoveResponseHeader

  • RemoveResponseHeader刪除響應header中的指定值
  • 下面的配置會刪除響應header中的foo:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/hello/**
          filters:
          - RemoveResponseHeader=foo

RemoveRequestParameter

  • RemoveRequestParameter 刪除請求引數中的指定引數
  • 下面的配置會刪除請求引數中的foo:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/hello/**
          filters:
          - RemoveRequestParameter=foo1

RewritePath

  • RewritePath非常實用,將請求引數中的路徑做變換
  • 下面的配置會將<font color="blue">/test/str</font>轉成<font color="blue">/hello/str</font>:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/test/**
          filters:
          - RewritePath=/test/?(?<segment>.*), /hello/$\{segment}
  • 請求如下,可見path中的test會被閘道器修改成hello,變成正確的請求路徑:

在這裡插入圖片描述

RewriteLocationResponseHeader

  • RewriteLocationResponseHeader用於改寫response中的location資訊
  • 配置如下,一共是四個引數:stripVersionMode、locationHeaderName、hostValue、protocolsRegex
  • 例如請求是<font color="blue">api.example.com/some/object/name</font>,response的location是<font color="blue">object-service.prod.example.net/v2/some/object/id</font>,最終會被下面的filter改寫為<font color="blue">api.example.com/some/object/id</font>
spring:
  cloud:
    gateway:
      routes:
      - id: rewritelocationresponseheader_route
        uri: http://example.org
        filters:
        - RewriteLocationResponseHeader=AS_IN_REQUEST, Location, ,
  • stripVersionMode的策略一共三種:

NEVER_STRIP:不執行
AS_IN_REQUEST :原始請求沒有vesion,就執行
ALWAYS_STRIP :固定執行

  • Location用於替換host:port部分,如果沒有就是用Request中的host
  • protocolsRegex用於匹配協議,如果匹配不上,name過濾器啥都不做

RewriteResponseHeader

  • RewriteResponseHeader很好理解:修改響應header,引數有三個:header的key,匹配value的正規表示式,修改value的結果
  • 下面的配置表示修改響應header中<font color="blue">X-Response-Red</font>這個key的value,找到password=xxx的內容,改成password=*
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/test/**
          filters:
          - RewriteResponseHeader=X-Response-Red, , password=[^&]+, password=***

SecureHeaders

  • SecureHeaders會在響應的header中新增很多和安全相關的內容,配置如下:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/hello/**
          filters:
          - SecureHeaders
  • 響應如下,可見header中新增了很多資訊:

在這裡插入圖片描述

  • 如果不想返回上圖中的某些內容,可以在配置檔案中關閉掉,如下圖紅框,x-frame-options和strict-transport-security兩項被設定為不返回了:

在這裡插入圖片描述

  • 再試試,得到如下響應,可見x-frame-options和strict-transport-security都沒有返回:

在這裡插入圖片描述

SetPath

  • SetPath配合predicates使用,下面的配置會將請求<font color="blue">/test/str</font>改成<font color="blue">/hello/str</font>,可見這個segment是在predicates中賦值的,然後再filters中拿來用:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      filter:
        secure-headers:
          disable:
            - x-frame-options
            - strict-transport-security
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/test/{segment}
          filters:
            - SetPath=/hello/{segment}

SetRequestHeader

  • SetRequestHeader顧名思義,就是改寫請求的header,將指定key改為指定value,如果該key不存在就建立:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      filter:
        secure-headers:
          disable:
            - x-frame-options
            - strict-transport-security
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/hello/**
          filters:
            - SetRequestHeader=X-Request-Red, Blue
  • 和SetPath類似,SetRequestHeader也可以和predicates配合,在predicates中定義的變數可以用在SetRequestHeader中,如下所示,當請求是/hello/str的時候,header中X-Request-Red的值就是<font color="blue">Blue-str</font>:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      filter:
        secure-headers:
          disable:
            - x-frame-options
            - strict-transport-security
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/hello/{segment}
          filters:
            - SetRequestHeader=X-Request-Red, Blue-{segment}

SetResponseHeader

  • SetResponseHeader顧名思義,就是改寫響應的header,將指定key改為指定value,如果該key不存在就建立:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      filter:
        secure-headers:
          disable:
            - x-frame-options
            - strict-transport-security
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/hello/**
          filters:
            - SetResponseHeader=X-Request-Red, Blue

SetStatus

  • SetStatus很好理解:控制返回code,下面的設定會返回500:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/hello/**
          filters:
            - SetStatus=500
  • 測試效果如下圖,服務提供者的內容會正常返回,但是返回碼已經被改為500了:

在這裡插入圖片描述

  • 如果您想用SetStatus修改返回碼,同時又不想丟掉真實的返回碼,可以增加如下配置,這樣真實的返回碼就被放在名為<font color="blue">original-status-header-name</font>的key中了:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      set-status:
        original-status-header-name: aaabbbccc
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/hello/**
          filters:
            - SetStatus=500

StripPrefix

  • StripPrefix是個很常用的filter,例如請求是<font color="blue">/aaa/bbb/hello/str</font>,我們要想將其轉為<font color="blue">/hello/str</font>,用<font color="blue">StripPrefix=2</font>即可,前面兩級path都被刪掉了:
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      set-status:
        original-status-header-name: aaabbbccc
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/aaa/**
          filters:
            - StripPrefix=2
  • 如下圖,響應正常:

在這裡插入圖片描述

Retry

  • 顧名思義,Retry就是重試,需要以下引數配合使用:
  1. retries:重試次數
  2. statuses:遇到什麼樣的返回狀態才重試,取值參考:org.springframework.http.HttpStatus
  3. methods:那些型別的方法會才重試(GET、POST等),取值參考:org.springframework.http.HttpMethod
  4. series:遇到什麼樣的series值才重試,取值參考:org.springframework.http.HttpStatus.Series
  5. exceptions:遇到什麼樣的異常才重試
  6. backoff:重試策略,由多個引數構成,例如firstBackoff
  • 參考配置如下:
spring:
  cloud:
    gateway:
      routes:
      - id: retry_test
        uri: http://localhost:8080/flakey
        predicates:
        - Host=*.retry.com
        filters:
        - name: Retry
          args:
            retries: 3
            statuses: BAD_GATEWAY
            methods: GET,POST
            backoff:
              firstBackoff: 10ms
              maxBackoff: 50ms
              factor: 2
              basedOnPreviousValue: false

RequestSize

  • RequestSize也很常用:控制請求大小,可以使用<font color="blue">KB</font>或者<font color="blue">MB</font>等單位,超過這個大小就會返回413錯誤(Payload Too Large),
spring:
  cloud:
    gateway:
      routes:
      - id: request_size_route
        uri: http://localhost:8080/upload
        predicates:
        - Path=/upload
        filters:
        - name: RequestSize
          args:
            maxSize: 5000000
  • 注意,如果沒有設定RequestSize,Spring Cloud Gateway預設的上限是<font color="red">5MB</font>

SetRequestHostHeader

  • SetRequestHostHeader會修改請求header中的host值
  • 下面的配置,會將請求header中的host改為<font color="blue">aaabbb</font>
server:
  #服務埠
  port: 8081
spring:
  application:
    name: hello-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/hello/**
          filters:
        - name: SetRequestHostHeader
        args:
          host: aaabbb
  • 在服務提供者的程式碼中打斷點,如下圖,可見host已經被改為<font color="blue">aaabbb</font>

在這裡插入圖片描述

ModifyRequestBody

  • ModifyRequestBody用於修改請求的body內容,這裡官方推薦用程式碼來配置,如下所示,請求body中原本是字串,結果被改成了Hello物件的例項:

    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
      return builder.routes()
          .route("rewrite_request_obj", r -> r.host("*.rewriterequestobj.org")
              .filters(f -> f.prefixPath("/httpbin")
                  .modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE,
                      (exchange, s) -> return Mono.just(new Hello(s.toUpperCase())))).uri(uri))
          .build();
    }
    
    static class Hello {
      String message;
    
      public Hello() { }
    
      public Hello(String message) {
          this.message = message;
      }
    
      public String getMessage() {
          return message;
      }
    
      public void setMessage(String message) {
          this.message = message;
      }
    }

ModifyResponseBody

  • ModifyResponseBody與前面的ModifyRequestBody類似,官方建議用程式碼實現,下面的程式碼作用是將響應body的內容改為全部大寫:
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("rewrite_response_upper", r -> r.host("*.rewriteresponseupper.org")
            .filters(f -> f.prefixPath("/httpbin")
                .modifyResponseBody(String.class, String.class,
                    (exchange, s) -> Mono.just(s.toUpperCase()))).uri(uri))
        .build();
}

TokenRelay

  • 在使用第三方鑑權的時候,如OAuth2,用TokenRelay可以將第三方的token轉發到服務提供者那裡去:
spring:
  cloud:
    gateway:
      routes:
      - id: resource
        uri: http://localhost:9000
        predicates:
        - Path=/resource
        filters:
        - TokenRelay=
  • 記得還要新增jar包依賴<font color="blue">org.springframework.boot:spring-boot-starter-oauth2-client</font>

設定全域性filter

  • 前面的例子中,所有filter都放在路由策略中,配合predicates一起使用的,如果您想配置全域性生效的filter,可以在配置檔案中做以下設定,下面的配置表示AddResponseHeader和PrefixPath會處理所有請求,和路由設定無關:
spring:
  cloud:
    gateway:
      default-filters:
      - AddResponseHeader=X-Response-Default-Red, Default-Blue
      - PrefixPath=/httpbin
  • 至此,大部分內建過濾器我們們已經瞭解了,有幾個略微複雜的留待後面的章節深入學習

你不孤單,欣宸原創一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 資料庫+中介軟體系列
  6. DevOps系列

歡迎關注公眾號:程式設計師欣宸

微信搜尋「程式設計師欣宸」,我是欣宸,期待與您一同暢遊Java世界...
https://github.com/zq2599/blog_demos

相關文章