1. 序章

Spring Cloud Gateway は、マイクロサービスでよく使用されるインテリジェントプロキシサービスです。 リクエストを単一のエントリポイントに透過的に集中化し、適切なサービスにルーティングします。 その最も興味深い機能の1つは、フィルターWebFilterまたはGatewayFilter)の概念です。

WebFilter、は、述語ファクトリとともに、完全なルーティングメカニズムを組み込んでいます。 Spring Cloud Gatewayは、プロキシサービスに到達する前にHTTPリクエストを操作し、結果をクライアントに配信する前にHTTP応答を操作できるようにする多くの組み込みWebFilterファクトリを提供しますカスタムフィルターを実装することも可能です。

このチュートリアルでは、プロジェクトに含まれている組み込みの WebFilter ファクトリと、それらを高度なユースケースで使用する方法に焦点を当てます。

2. WebFilterファクトリ

WebFilter (または GatewayFilter )ファクトリでは、インバウンドHTTP要求とアウトバウンドHTTP応答を変更できます。 この意味で、ダウンストリームサービスとの対話の前後に適用する一連の興味深い機能を提供します。

ハンドラーマッピングは、クライアントの要求を管理します。 設定されたルートと一致するかどうかをチェックします。 次に、このルートの特定のフィルターチェーンを実行する要求をWebハンドラーに送信します。 点線は、ロジックをフィルター前のロジックとフィルター後のロジックに分割します。 収入フィルターは、プロキシ要求の前に実行されます。 出力フィルターは、プロキシ応答を受信すると動作を開始します。 フィルタは、その間のプロセスを変更するメカニズムを提供します。

3. WebFilterファクトリの実装

Spring Cloudゲートウェイプロジェクトに組み込まれている最も重要なWebFilterファクトリを確認しましょう。 YAMLまたはJavaDSLを使用して、それらを実装する2つの方法があります。 両方を実装する方法の例を示します。

3.1. HTTPリクエスト

組み込みのWebFilterファクトリを使用すると、HTTPリクエストのヘッダーとパラメーターを操作できます。 add (AddRequestHeader)、 map (MapRequestHeader) set or replace (SetRequestHeader )、および remove (RemoveRequestHeader)ヘッダー値を、プロキシされたサービスに送信します。 元のホストヘッダーを保持することもできます( ReserveHostHeader )。

同様に、 add (AddRequestParameter)および remove (RemoveRequestParameter)パラメーターをダウンストリームサービスで処理できます。 それを行う方法を見てみましょう:

- id: add_request_header_route
  uri: https://httpbin.org
  predicates:
  - Path=/get/**
  filters:
  - AddRequestHeader=My-Header-Good,Good
  - AddRequestHeader=My-Header-Remove,Remove
  - AddRequestParameter=var, good
  - AddRequestParameter=var2, remove
  - MapRequestHeader=My-Header-Good, My-Header-Bad
  - MapRequestHeader=My-Header-Set, My-Header-Bad
  - SetRequestHeader=My-Header-Set, Set 
  - RemoveRequestHeader=My-Header-Remove
  - RemoveRequestParameter=var2

すべてが期待どおりに機能するかどうかを確認しましょう。 そのために、curlと公開されているhttpbin.orgを使用します。

$ curl http://localhost:8080/get
{
  "args": {
    "var": "good"
  },
  "headers": {
    "Host": "localhost",
    "My-Header-Bad": "Good",
    "My-Header-Good": "Good",
    "My-Header-Set": "Set",
  },
  "origin": "127.0.0.1, 90.171.125.86",
  "url": "https://localhost:8080/get?var=good"
}

構成されたリクエストフィルターの結果としてのcurl応答を確認できます。 My-Header-Goodに値Goodを追加し、そのコンテンツをMy-Header-Badにマップします。My-Header-Remove[を削除しますX137X]を選択し、新しい値をMy-Header-Setに設定します。 argsおよびurlセクションに、新しいパラメーターvarが追加されていることがわかります。 さらに、最後のフィルターはvar2パラメーターを削除します。

さらに、プロキシサービスに到達する前にリクエスト本文を変更できます。 このフィルターは、JavaDSL表記を使用してのみ構成できます。 以下のスニペットは、応答本文のコンテンツを大文字にしています。

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
     return builder.routes()
       .route("modify_request_body", r -> r.path("/post/**")
         .filters(f -> f.modifyRequestBody(
           String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, 
           (exchange, s) -> Mono.just(new Hello(s.toUpperCase()))))
         .uri("https://httpbin.org"))
       .build();
}

スニペットをテストするために、 -d オプションを指定してcurlを実行し、本文「Content」を含めます。

$ curl -X POST "http://localhost:8080/post" -i -d "Content"
"data": "{\"message\":\"CONTENT\"}",
"json": {
    "message": "CONTENT"
}

フィルタの結果、本文のコンテンツがCONTENTに大文字になっていることがわかります。

3.2. HTTP応答

同様に、応答ヘッダーは、 add AddResponseHeader )、 set 、またはreplace( SetResponseHeader )を使用して変更できます。 、 remove RemoveResponseHeader )および rewrite RewriteResponseHeader )。 応答のもう1つの機能は、 dedupe DedupeResponseHeader)を使用して、戦略を上書きし、戦略の重複を回避することです。 別の組み込みファクトリ( RemoveLocationResponseHeader )を使用して、バージョン、場所、およびホストに関するバックエンド固有の詳細を取り除くことができます。

完全な例を見てみましょう:

- id: response_header_route
  uri: https://httpbin.org
  predicates:
  - Path=/header/post/**
  filters:
  - AddResponseHeader=My-Header-Good,Good
  - AddResponseHeader=My-Header-Set,Good
  - AddResponseHeader=My-Header-Rewrite, password=12345678
  - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
  - AddResponseHeader=My-Header-Remove,Remove
  - SetResponseHeader=My-Header-Set, Set
  - RemoveResponseHeader=My-Header-Remove
  - RewriteResponseHeader=My-Header-Rewrite, password=[^&]+, password=***
  - RewriteLocationResponseHeader=AS_IN_REQUEST, Location, ,

curlを使用して応答ヘッダーを表示してみましょう。

$ curl -X POST "http://localhost:8080/header/post" -s -o /dev/null -D -
HTTP/1.1 200 OK
My-Header-Good: Good
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
My-Header-Rewrite: password=***
My-Header-Set: Set

HTTPリクエストと同様に、応答本文を変更できます。 この例では、PUT応答の本文を上書きします。

@Bean
public RouteLocator responseRoutes(RouteLocatorBuilder builder) {
    return builder.routes()
      .route("modify_response_body", r -> r.path("/put/**")
        .filters(f -> f.modifyResponseBody(
          String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, 
          (exchange, s) -> Mono.just(new Hello("New Body"))))
        .uri("https://httpbin.org"))
      .build();
}

PUTエンドポイントを使用して機能をテストしてみましょう。

$ curl -X PUT "http://localhost:8080/put" -i -d "CONTENT"
{"message":"New Body"}

3.3. 道

組み込みのWebFilterファクトリで提供される機能の1つは、クライアントによって構成されたパスとの相互作用です。 別のパスを設定 SetPath )、書き換え書き換えパス)、プレフィックスを追加することができます( prefixPath )、および strip StripPrefix )を使用して、その一部のみを抽出します。 フィルタは、YAMLファイル内の位置に基づいて順番に実行されることに注意してください。 ルートを構成する方法を見てみましょう。

- id: path_route
  uri: https://httpbin.org
  predicates:
  - Path=/new/post/**
  filters:
  - RewritePath=/new(?<segment>/?.*), $\{segment}
  - SetPath=/post

どちらのフィルターも、プロキシされたサービスに到達する前にサブパス /newを削除します。 curlを実行してみましょう:

$ curl -X POST "http://localhost:8080/new/post" -i
"X-Forwarded-Prefix": "/new"
"url": "https://localhost:8080/post"

StripPrefixファクトリを使用することもできます。 StripPrefix = 1の場合、ダウンストリームサービスに接続するときに最初のサブパスを取り除くことができます。

3.4. HTTPステータスに関連

RedirectTo は、ステータスとURLの2つのパラメーターを取ります。 ステータスは一連の300リダイレクトHTTPコードであり、URLは有効なものである必要があります。 SetStatus は、HTTPコードまたはその文字列表現である可能性のある1つのパラメーターステータスを取ります。 いくつかの例を見てみましょう。

- id: redirect_route
  uri: https://httpbin.org
  predicates:
  - Path=/fake/post/**
  filters:
  - RedirectTo=302, https://httpbin.org
- id: status_route
  uri: https://httpbin.org
  predicates:
  - Path=/delete/**
  filters:
  - SetStatus=401

最初のフィルターは/fake/ postパスに対して機能し、クライアントはHTTPステータス302でhttps://httpbin.orgにリダイレクトされます。

$ curl -X POST "http://localhost:8080/fake/post" -i
HTTP/1.1 302 Found
Location: https://httpbin.org

2番目のフィルターが/deleteパスを検出し、HTTPステータス401が設定されます。

$ curl -X DELETE "http://localhost:8080/delete" -i
HTTP/1.1 401 Unauthorized

3.5. リクエストサイズの制限

最後に、リクエストのサイズ制限制限できます( RequestSize )。 リクエストサイズが制限を超えている場合、ゲートウェイはサービスへのアクセスを拒否します

- id: size_route
  uri: https://httpbin.org
  predicates:
  - Path=/anything
  filters:
  - name: RequestSize
    args:
       maxSize: 5000000

4. 高度なユースケース

Spring Cloud Gatewayは、マイクロサービスパターンのベースライン機能をサポートするために、他の高度なWebFilterファクトリを提供します。

4.1. サーキットブレーカ

Spring Cloud Gatewayには、サーキットブレーカー機能用の組み込みWebFilterファクトリがあります。 工場では、さまざまなフォールバック戦略とJavaDSLルート構成が許可されています。 簡単な例を見てみましょう。

- id: circuitbreaker_route
  uri: https://httpbin.org
  predicates:
  - Path=/status/504
  filters:
  - name: CircuitBreaker
  args:
     name: myCircuitBreaker
     fallbackUri: forward:/anything
  - RewritePath=/status/504, /anything

サーキットブレーカーの構成では、 spring-cloud-starter-circuitbreaker-reactor-resilience4j 依存関係を追加して、Resilience4Jを使用しました。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>

ここでも、curlを使用して機能をテストできます。

$ curl http://localhost:8080/status/504 
"url": "https://localhost:8080/anything"

4.2. リトライ

もう1つの高度な機能を使用すると、プロキシされたサービスで何かが発生したときにクライアントがアクセスを再試行できます。 再試行回数、再試行する必要のあるHTTPステータスコード(ステータス)、メソッドシリーズなどのいくつかのパラメーターが必要です。 ]、例外、およびバックオフの間隔で、各再試行後に待機します。 YAML構成を見てみましょう:

- id: retry_test
  uri: https://httpbin.org
  predicates:
  - Path=/status/502
  filters:
  - name: Retry
    args:
       retries: 3
       statuses: BAD_GATEWAY
       methods: GET,POST
       backoff:
          firstBackoff: 10ms
          maxBackoff: 50ms
          factor: 2
          basedOnPreviousValue: false

クライアントが/status / 502 (Bad Gateway)に達すると、フィルターは3回再試行し、各実行後に構成されたバックオフ間隔を待ちます。 それがどのように機能するか見てみましょう:

$ curl http://localhost:8080/status/502

同時に、サーバーのゲートウェイログを確認する必要があります。

Mapping [Exchange: GET http://localhost:8080/status/502] to Route{id='retry_test', ...}
Handler is being applied: {uri=https://httpbin.org/status/502, method=GET}
Received last HTTP packet
Handler is being applied: {uri=https://httpbin.org/status/502, method=GET}
Received last HTTP packet
Handler is being applied: {uri=https://httpbin.org/status/502, method=GET}
Received last HTTP packet

ゲートウェイがステータス502を受信すると、フィルターはメソッドGETおよびPOSTに対してこのバックオフを使用して3回再試行します。

4.3. セッションとセキュアヘッダーを保存する

SecureHeader ファクトリは、HTTPセキュリティヘッダーを応答[X80X]に追加します。 同様に、 SaveSession は、 SpringSessionおよびSpringSecurityとともに使用する場合に特に重要です。

filters: 
- SaveSession

このフィルタは、転送されたコールを行う前にセッション状態を保存します。

4.4. リクエストレートリミッター

最後になりましたが、 RequestRateLimiter ファクトリは、要求を続行できるかどうかを決定します。  そうでない場合は、HTTPコードステータス429– Too ManyRequestsを返します。 レートリミッターを指定するためにさまざまなパラメーターとリゾルバーを使用します。

RedisRateLimiter は、よく知られている Redis データベースを使用して、バケットが保持できるトークンの数を確認します。 次の依存関係が必要です。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
 </dependency>

したがって、 SpringRedisの構成も必要です。

spring:
  redis:
    host: localhost
    port: 6379

フィルタにはいくつかのプロパティがあります。 最初の引数replenishRate、は、許可される1秒あたりのリクエスト数です。 2番目の引数burstCapacity、は、1秒間のリクエストの最大数です。 3番目のパラメーターrequestedTokens、は、リクエストにかかるトークンの数です。 実装例を見てみましょう。

- id: request_rate_limiter
  uri: https://httpbin.org
  predicates:
  - Path=/redis/get/**
  filters:
  - StripPrefix=1
  - name: RequestRateLimiter
    args:
       redis-rate-limiter.replenishRate: 10
       redis-rate-limiter.burstCapacity: 5

curlを使用してフィルターをテストしてみましょう。 事前に、 Redis インスタンスを開始することを忘れないでください。たとえば、Dockerを使用します。

$ curl "http://localhost:8080/redis/get" -i
HTTP/1.1 200 OK
X-RateLimit-Remaining: 4
X-RateLimit-Requested-Tokens: 1
X-RateLimit-Burst-Capacity: 5
X-RateLimit-Replenish-Rate: 10

残りのレート制限がゼロに達すると、ゲートウェイはHTTPコード429を発生させます。 動作をテストするために、単体テストを使用できます。 Embedded Redis Server を起動し、RepeatedTestsを並行して実行します。 バケットが制限に達すると、エラーが表示され始めます。

00:57:48.263 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->200, reason->OK, remaining->[4]
00:57:48.394 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->200, reason->OK, remaining->[3]
00:57:48.530 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->200, reason->OK, remaining->[2]
00:57:48.667 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->200, reason->OK, remaining->[1]
00:57:48.826 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->200, reason->OK, remaining->[0]
00:57:48.851 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->429, reason->Too Many Requests, remaining->[0]
00:57:48.894 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->429, reason->Too Many Requests, remaining->[0]
00:57:49.135 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->200, reason->OK, remaining->[4]

5. 結論

このチュートリアルでは、Spring CloudGatewayのWebFilterファクトリについて説明しました。 プロキシされたサービスを実行する前後に、クライアントからの要求と応答を操作する方法を示しました。

いつものように、コードはGitHubから入手できます。