Spring Cloud Gateway介紹(二)

Spring Cloud Gateway介紹(一)

 

全局Filters

GlobalFilter與GatewayFilter具有一樣的接口。

這些全局過濾器都有對應的配置類,例如:

  • org.springframework.cloud.gateway.config.GatewayAutoConfiguration
  • org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration
  • org.springframework.cloud.gateway.config.GatewayLoadBalancerClientAutoConfiguration

 

聚合全局Filter和GatewayFilter並排序

當一個Request匹配路由,則會把所有全局Filter和GatewayFilter組合成一個Filter Chain,並通過org.springframework.core.Ordered接口進行排序,數值越小優先級越高,高優先級在pre階段排在前面,在post階段排在後面。

Foward Routing Filter

用於本地forward,也就是將請求在Gateway服務內進行轉發,而不是轉發到下游服務。

當請求進來時,ForwardRoutingFilter 會查看一個URL,該URL爲 exchange 屬性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值,如果該 url 的 scheme(協議) 是 forward(例如:forward://localendpoint),那麼該Filter會使用Spirngd的DispatcherHandler 來處理這個請求。該請求的URL路徑部分,會被forward URL中的路徑覆蓋掉。而未修改過的原始URL,會被追加到 ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR 屬性中。

LoadBalancerClientFilter 

這個Filter是用來整合Ribbon的,其核心就是解析 scheme 爲lb的 url,以此獲取微服務的名稱,然後再通過Ribbon獲取實際的調用地址。

當請求進來時,LoadBalancerClientFilter 會查看一個URL,該URL爲 exchange 的屬性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值,如果該 url 的 scheme 是 lb,(例如:lb://myservice ),那麼該Filter會使用Spring Cloud的 LoadBalancerClient 來將 myservice 解析成實際的host 和 port ,並替換掉原本 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 屬性的值。而原始 url 會追加到 ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR 屬性中。該過濾器還會查看 ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR 屬性,如果發現該屬性的值是 lb ,也會執行相同邏輯。

spring:
  cloud:
    gateway:
      routes:
      - id: myRoute
        uri: lb://service
        predicates:
        - Path=/service/**

默認情況下,如果無法通過 LoadBalancer 找到指定服務的實例,那麼會返回503(如上配置示例, 若 LoadBalancer 找不到名爲 service 的實例時,就會返回503);可使用配置: spring.cloud.gateway.loadbalancer.use404=true ,讓其返回404。

LoadBalancer 返回的 ServiceInstance 的 isSecure 的值,會覆蓋請求的scheme。舉個例子,如果請求打到Gateway上使用的是 HTTPS ,但 ServiceInstance 的 isSecure 是false,那麼下游微服務接收到的則是HTTP請求,反之亦然。另外,如果該路由指定了 GATEWAY_SCHEME_PREFIX_ATTR 屬性,那麼前綴將會被剝離,並且路由URL中的scheme會覆蓋 ServiceInstance 的配置。

LoadBalancerClientFilter 默認使用的是一個阻塞式的LoadBalancerClient ,建議使用ReactiveLoadBalancerClientFilter來替換,可以通過設置spring.cloud.loadbalancer.ribbon.enabled=false來切換。

ReactiveLoadBalancerClientFilter 

功能同LoadBalancerClientFilter 。

NettyRoutingFilter

當請求進來時,NettyRoutingFilter 會查看一個URL,該URL是 exchange 的屬性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值,如果該 url 的 scheme 是 http 或 https ,那麼該Filter會使用 Netty 的 HttpClient 向下遊的服務發送代理請求。獲得的響應將放在 exchange 的 ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR 屬性中,以便在後面的 Filter 裏使用。(有一個實驗性的過濾器: WebClientHttpRoutingFilter 可實現相同功能,但無需Netty)

NettyWriteResponseFilter 

NettyWriteResponseFilter 用於將代理響應寫回網關的客戶端側,所以該過濾器會在所有其他過濾器執行完成後才執行,並且執行的條件是 exchange 中 ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR 屬性的值不爲空,該值爲 Netty 的 Connection 實例。(有一個實驗性的過濾器: WebClientWriteResponseFilter 可實現相同功能,但無需Netty)。

RouteToRequestUrlFilter

這個過濾器用於將從request裏獲取的原始url轉換成Gateway進行請求轉發時所使用的url。當請求進來時,RouteToRequestUrlFilter 會從 exchange 中獲取 ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR 屬性的值,該值是一個 Route 對象。若該對象不爲空的話,RouteToRequestUrlFilter 會基於請求 URL 及 Route 對象裏的 URL 來創建一個新的 URL。新 URL 會被放到 exchange 的 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 屬性中。
如果 URL 具有 scheme 前綴,例如 lb:ws://serviceid ,該 lb scheme將從URL中剝離,並放到 ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR 中,方便後面的過濾器使用。

WebSocket Routing Filter

該過濾器的作用與 NettyRoutingFilter 類似。當請求進來時,WebsocketRoutingFilter 會查看一個URL,該URL是 exchange 中 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 屬性的值,如果該 url 的 scheme 是 ws 或者 wss,那麼該Filter會使用 Spring Web Socket 將 Websocket 請求轉發到下游。
另外,如果 Websocket 請求需要負載均衡的話,可爲URL添加 lb 前綴以實現負載均衡,例如 lb:ws://serviceid 。

pring:
  cloud:
    gateway:
      routes:
      # SockJS route
      - id: websocket_sockjs_route
        uri: http://localhost:3001
        predicates:
        - Path=/websocket/info/**
      # Normal Websocket route
      - id: websocket_route
        uri: ws://localhost:3001
        predicates:
        - Path=/websocket/**

Gateway Metrics Filter

想要啓用Gateway Metrics Filter,需在項目中添加 spring-boot-starter-actuator 依賴,然後在配置文件中配置 spring.cloud.gateway.metrics.enabled 的值爲true。該過濾器會添加名爲 gateway.requests 的時序度量(timer metric),其中包含以下標記:

  • routeId:路由ID
  • routeUri:API將路由到的URI
  • outcome:由 HttpStatus.Series 分類
  • status:返回給客戶端的Http Status
  • httpStatusCode:返回給客戶端的請求的Http Status
  • httpMethod:請求所使用的Http方法
    這些指標暴露在 /actuator/metrics/gateway.requests 端點中,並且可以輕鬆與 Prometheus 整合,從而創建一個 Grafana dashboard。

Marking An Exchange As Routed

當一個請求走完整條過濾器鏈後,負責轉發請求到下游的那個過濾器會在 exchange 中添加一個 gatewayAlreadyRouted 屬性,從而將 exchange 標記爲 routed(已路由)。一旦請求被標記爲 routed ,其他路由過濾器將不會再次路由該請求,而是直接跳過。
瞭解了以上所有內置的全局過濾器後,我們知道不同協議的請求會由不同的過濾器轉發到下游。所以負責添加這個gatewayAlreadyRouted 屬性的過濾器就是最終負責轉發請求的過濾器:

  • http、https請求會由NettyRoutingFilterWebClientHttpRoutingFilter添加這個屬性
  • forward請求會由ForwardRoutingFilter添加這個屬性
  • websocket請求會由WebsocketRoutingFilter添加這個屬性
    這些過濾器調用了以下方法將 exchange 標記爲 routed ,或檢查 exchange 是否是 routed
  • ServerWebExchangeUtils.isAlreadyRouted:檢查exchange是否爲routed狀態
  • ServerWebExchangeUtils.setAlreadyRouted:將exchange設置爲routed狀態
    簡單來說,就是Gateway通過 gatewayAlreadyRouted 屬性表示這個請求已經轉發過了,而無需其他過濾器重複路由,從而防止重複的路由轉發。

HttpHeadersFilters

HttpHeadersFilters 在請求往下游發送之前應用到headers,例如:NettyRoutingFilter。

Fowarded Headers Filter

添加Fowarded header,或者添加host header,當前請求的schema,port到已存在Fowarded header。

RemoveHopbyHop Headers Filter

從一個已Fowarded的請求中刪除某些header,默認值(來源於IETF)爲:

  • Connection

  • Keep-Alive

  • Proxy-Authenticate

  • Proxy-Authorization

  • TE

  • Trailer

  • Transfer-Encoding

  • Upgrade

可以通過設置spring.cloud.gateway.filter.remove-non-proxy-headers.headers來修改默認列表。

XFowarded Headers Filter

類似 Fowarded,添加X-Forwarded-*。

通過以下屬性控制(默認爲true):

  • spring.cloud.gateway.x-forwarded.for-enabled

  • spring.cloud.gateway.x-forwarded.host-enabled

  • spring.cloud.gateway.x-forwarded.port-enabled

  • spring.cloud.gateway.x-forwarded.proto-enabled

  • spring.cloud.gateway.x-forwarded.prefix-enabled

可以通過設置以下屬性(默認爲true)來控制是否append:

  • spring.cloud.gateway.x-forwarded.for-append

  • spring.cloud.gateway.x-forwarded.host-append

  • spring.cloud.gateway.x-forwarded.port-append

  • spring.cloud.gateway.x-forwarded.proto-append

  • spring.cloud.gateway.x-forwarded.prefix-append

TSL和SSL

gateway可以通過以下配置監聽HTTPS請求:

server:
  ssl:
    enabled: true
    key-alias: scg
    key-store-password: scg1234
    key-store: classpath:scg-keystore.p12
    key-store-type: PKCS12

網關路由可以路由到 http 和 https 後端。如果路由到 https 後端,則可以將網關配置爲信任具有以下配置的所有下游證書:

spring:
  cloud:
    gateway:
      httpclient:
        ssl:
          useInsecureTrustManager: true

使用不安全的信任管理器不適合生產。對於生產部署,可以使用以下配置配置一組可信任的已知證書:

spring:
  cloud:
    gateway:
      httpclient:
        ssl:
          trustedX509Certificates:
          - cert1.pem
          - cert2.pem

 如果Spring Cloud Gateway未配置可信證書,則使用默認信任庫(可以使用系統屬性javax.net.ssl.trustStore覆蓋)。

TSL握手

網關維護一個客戶端池,用於路由到後端。通過https進行通信時,客戶端會啓動TLS握手。這次握手會有很多超時。可以配置這些超時(顯示默認值):

  cloud:
    gateway:
      httpclient:
        ssl:
          handshake-timeout-millis: 10000
          close-notify-flush-timeout-millis: 3000
          close-notify-read-timeout-millis: 0

 Configuration

Spring Cloud Gateway 的配置是由一系列的 RouteDefinitionLocator 類來管理的。

public interface RouteDefinitionLocator {
    Flux<RouteDefinition> getRouteDefinitions();
}

默認情況下,PropertiesRouteDefinitionLocator 使用 Spring Boot 的@ConfigurationProperties 機制加載屬性。

上面的配置示例都使用了一個使用位置參數而不是命名參數的快捷符號。以下兩個例子是等效的:

spring:
  cloud:
    gateway:
      routes:
      - id: setstatus_route
        uri: https://example.org
        filters:
        - name: SetStatus
          args:
            status: 401
      - id: setstatusshortcut_route
        uri: https://example.org
        filters:
        - SetStatus=401

對於網關的一些用法,屬性是足夠的,但是一些生產用例將受益於從外部源(例如數據庫)加載配置。未來的里程碑版本將RouteDefinitionLocator基於Spring Data Repositories實現,例如:Redis,MongoDB和Cassandra。

Route Metadata Configuration

可以使用metadata爲每個route增加附加屬性。

spring:
  cloud:
    gateway:
      routes:
      - id: route_with_metadata
        uri: https://example.org
        metadata:
          optionName: "OptionValue"
          compositeObject:
            name: "value"
          iAmNumber: 1

可以通過exchange獲取所有屬性。

Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
// get all metadata properties
route.getMetadata();
// get a single metadata property
route.getMetadata(someKey);

 HTTP超時配置

全局超時

  • connect-timeout:單位:毫秒.
  • response-timeout :單位: java.time.Duration
pring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 1000
        response-timeout: 5s

每個路由配置

connect-timeout :單位:毫秒.
response-timeout:單位:毫秒

      - id: per_route_timeouts
        uri: https://example.org
        predicates:
          - name: Path
            args:
              pattern: /delay/{timeout}
        metadata:
          response-timeout: 200
          connect-timeout: 200

JAVA代碼:

import static org.springframework.cloud.gateway.support.RouteMetadataUtils.CONNECT_TIMEOUT_ATTR;
import static org.springframework.cloud.gateway.support.RouteMetadataUtils.RESPONSE_TIMEOUT_ATTR;

      @Bean
      public RouteLocator customRouteLocator(RouteLocatorBuilder routeBuilder){
         return routeBuilder.routes()
               .route("test1", r -> {
                  return r.host("*.somehost.org").and().path("/somepath")
                        .filters(f -> f.addRequestHeader("header1", "header-value-1"))
                        .uri("http://someuri")
                        .metadata(RESPONSE_TIMEOUT_ATTR, 200)
                        .metadata(CONNECT_TIMEOUT_ATTR, 200);
               })
               .build();
      }

流式JAVA路由API

爲了允許在Java中進行簡單配置,RouteLocatorBuilder 中定義了一個流式的API 

// static imports from GatewayFilters and RoutePredicates
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder, ThrottleGatewayFilterFactory throttle) {
    return builder.routes()
            .route(r -> r.host("**.abc.org").and().path("/image/png")
                .filters(f -> f.addResponseHeader("X-TestHeader", "foobar"))
                .uri("http://httpbin.org:80")
            )
            .route(r -> r.path("/image/webp")
                .filters(f -> f.addResponseHeader("X-AnotherHeader", "baz"))
                .uri("http://httpbin.org:80")
                .metadata("key", "value")
            )
            .route(r -> r.order(-1)
                .host("**.throttle.org").and().path("/get")
                .filters(f -> f.filter(throttle.apply(1,
                        1,
                        10,
                        TimeUnit.SECONDS)))
                .uri("http://httpbin.org:80")
                .metadata("key", "value")
            )
            .build();
}

這類風格也允許更多的自定義謂詞斷言。RouteDefinitionLocator  定義的謂詞使用邏輯與組合。通過使用流式的 Java API,您可以在 Predicate 類上使用and(),or()和 negate()運算符。

DiscoveryClient Route Definition Locator

可以將網關配置爲基於在 DiscoveryClient 兼容服務註冊中心註冊的服務來創建路由。要啓用此功能,請設置 spring.cloud.gateway.discovery.locator.enabled = true 並確保DiscoveryClient 實現類位於classpath下並已啓用(例如 Netflix Eureka,Consul 或Zookeeper)。

配置Predicates和Filters

默認情況下,DiscoveryClient創建的Routes僅定義了一個predicate和filer,

        predicates:
        - Path=/serviceId/**
        filters:
        - RewritePath=/serviceId/(?<remaining>.*), /$\{remaining} 

如果想定義多個,則通過以下配置:

spring.cloud.gateway.discovery.locator.predicates[0].name: Path
spring.cloud.gateway.discovery.locator.predicates[0].args[pattern]: "'/'+serviceId+'/**'"
spring.cloud.gateway.discovery.locator.predicates[1].name: Host
spring.cloud.gateway.discovery.locator.predicates[1].args[pattern]: "'**.foo.com'"
spring.cloud.gateway.discovery.locator.filters[0].name: Hystrix
spring.cloud.gateway.discovery.locator.filters[0].args[name]: serviceId
spring.cloud.gateway.discovery.locator.filters[1].name: RewritePath
spring.cloud.gateway.discovery.locator.filters[1].args[regexp]: "'/' + serviceId + '/(?<remaining>.*)'"
spring.cloud.gateway.discovery.locator.filters[1].args[replacement]: "'/${remaining}'"

CORS配置

網關可以通過配置控制 CORS 行爲。 “全局”CORS 配置是 Spring Framework CorsConfiguration的 URL 模式映射。

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "https://docs.spring.io"
            allowedMethods:
            - GET

在上面的示例中,對於所有 GET 請求的路徑,將允許來自 docs.spring.io 的請求的 CORS請求。

Gateway應如何使用MVC或Webflux搭建

Spring Cloud Gateway提供了一個實用程序對象ProxyExchange,可以在常規Spring Web處理程序中將其用作方法參數。它通過反射HTTP謂詞的方法支持基本的下游HTTP交換。使用MVC,它還支持通過該forward()方法轉發到本地處理程序。要在類路徑中使用ProxyExchange,只需包含正確的模塊(spring-cloud-gateway-mvc或者spring-cloud-gateway-webflux)。

MVC 示例(代理一個到下游遠程服務器的“/test”請求): 

@RestController
@SpringBootApplication
public class GatewaySampleApplication {

    @Value("${remote.home}")
    private URI home;

    @GetMapping("/test")
    public ResponseEntity<?> proxy(ProxyExchange<byte[]> proxy) throws Exception {
        return proxy.uri(home.toString() + "/image/png").get();
    }

}

Flux:

@RestController
@SpringBootApplication
public class GatewaySampleApplication {

    @Value("${remote.home}")
    private URI home;

    @GetMapping("/test")
    public Mono<ResponseEntity<?>> proxy(ProxyExchange<byte[]> proxy) throws Exception {
        return proxy.uri(home.toString() + "/image/png").get();
    }

}

 

Spring MVC 或 Webflux 的所有特性都可用於 Gateway 處理方法。例如,您可以注入請求頭和查詢參數,並且您可以使用映射註釋中的聲明來約束傳入的請求。

可以使用 ProxyExchange 上的 header()方法將頭信息添加到下游響應中。

您還可以通過向 get()等方法添加映射器來操作響應頭信息(以及響應中你喜歡的任何其他內容)。映射器是一個函數,它接收傳入的 ResponseEntity 並將其轉換爲傳出的ResponseEntity。

最高優先級的類支持處理讓"sensitive"敏感頭信息(默認情況下是"cookie"和"authorization")不往下游傳遞,並且對於"proxy"代理頭信息處理爲(x-forwarded-*)。
 

參考

屬性設置附錄:

https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/appendix.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章