1. 序章

Spring Cloud Gatewayの一般的な使用例は、1つ以上のサービスのファサードとして機能し、クライアントにそれらをより簡単に利用する方法を提供することです。

このチュートリアルでは、リクエストをバックエンドに送信する前にURLを書き換えることで、公開されたAPIをカスタマイズするさまざまな方法を示します。

2. SpringCloudGatewayの簡単な要約

Spring Cloud Gateway プロジェクトは、人気のあるSpringBoot2とProjectReactor の上に構築されているため、主な扱いを継承しています。

  • 反応性のおかげでリソース使用量が少ない
  • Spring Cloudエコシステムからのすべての機能のサポート(検出、構成など)
  • 標準のSpringパターンを使用して簡単に拡張および/またはカスタマイズできます

その主な機能については以前の記事ですでに説明したので、ここでは主な概念をリストします。

  • Route :一致する着信要求がゲートウェイで通過する一連の処理ステップ
  • Predicate ServerWebExchangeに対して評価されるJava8Predicate
  • Filters ServerWebExchangeを検査および/または変更できるGatewayFilterインスタンス。 ゲートウェイは、グローバルフィルターとルートごとのフィルターの両方をサポートします。

簡単に言うと、着信リクエストが通過する処理シーケンスは次のとおりです。

  • ゲートウェイは、各ルートに関連付けられた述語を使用して、どのルートが要求を処理するかを見つけます。
  • ルートが見つかると、リクエスト( ServerWebExchange インスタンス)は、最終的にバックエンドに送信されるまで、構成された各フィルターを通過します。
  • バックエンドが応答を送り返す場合、またはエラー(タイムアウトや接続のリセットなど)が発生した場合、フィルターは応答をクライアントに送り返す前に、応答を処理する機会を再び取得します。

3. 構成ベースのURL書き換え

この記事の主な主題に戻って、受信URLをバックエンドに送信する前に書き換えるルートを定義する方法を見てみましょう。 たとえば、 / api / v1 / customer / * の形式の着信リクエストがある場合、バックエンドURLはhttp://v1.customers/api/*である必要があります。 ここでは、「このポイントを超えるもの」を表すために「*」を使用しています。

構成ベースの書き換えを作成するには、アプリケーションの構成にいくつかのプロパティを追加する必要があります。 ここでは、わかりやすくするためにYAMLベースの構成を使用しますが、この情報は、サポートされているPropertySourceから取得できます。

spring:
  cloud:
    gateway:
      routes:
      - id: rewrite_v1
        uri: ${rewrite.backend.uri:http://example.com}
        predicates:
        - Path=/v1/customer/**
        filters:
        - RewritePath=/v1/customer/(?<segment>.*),/api/$\{segment}

この構成を分析してみましょう。 まず、ルートのIDがあります。これは単なる識別子です。 次に、uriプロパティで指定されたバックエンドURIがあります。 最終パスはリライトロジックから取得されるため、ホスト名/ポートのみが考慮されることに注意してください。

predicates プロパティは、このルートをアクティブ化するために満たす必要のある条件を定義します。 この例では、 Path 述語を使用します。この述語は、antのようなパス式を使用して、着信要求のパスと照合します。

最後に、filtersプロパティには実際の書き換えロジックがあります。 RewritePathフィルターは、正規表現と置換文字列の2つの引数を取ります。フィルターの実装は、提供されたパラメーターを引数として使用して、リクエストのURIで replaceAll()メソッドを実行するだけで機能します。 。

Springが構成ファイルを処理する方法の注意点は、Springがプロパティ参照であると見なし、その値を置き換えようとするため、標準の ${group}置換式を使用できないことです。 。 これを回避するには、実際の置換式として使用する前に、フィルターの実装によって削除される「$」文字と「{」文字の間に円記号を追加する必要があります。

4. DSLベースのURL書き換え

RewritePath は非常に強力で使いやすいですが、書き換えルールに動的な側面があるシナリオでは不十分です。 場合によっては、ルールの各ブランチのガードとして述語を使用して、複数のルールを作成できる場合があります。

ただし、そうでない場合は、DSLベースのアプローチを使用してルートを作成できます。必要なのは、ルートのロジックを実装する RouteLocatorbeanを作成することだけです。 。 例として、前と同じように、正規表現を使用して着信URIを書き換える単純なルートを作成しましょう。 ただし、今回は、要求ごとに置換文字列が動的に生成されます。

@Configuration
public class DynamicRewriteRoute {
    
    @Value("${rewrite.backend.uri}")
    private String backendUri;
    private static Random rnd = new Random();
    
    @Bean
    public RouteLocator dynamicZipCodeRoute(RouteLocatorBuilder builder) {
        return builder.routes()
          .route("dynamicRewrite", r ->
             r.path("/v2/zip/**")
              .filters(f -> f.filter((exchange, chain) -> {
                  ServerHttpRequest req = exchange.getRequest();
                  addOriginalRequestUrl(exchange, req.getURI());
                  String path = req.getURI().getRawPath();
                  String newPath = path.replaceAll(
                    "/v2/zip/(?<zipcode>.*)", 
                    "/api/zip/${zipcode}-" + String.format("%03d", rnd.nextInt(1000)));
                  ServerHttpRequest request = req.mutate().path(newPath).build();
                  exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI());
                  return chain.filter(exchange.mutate().request(request).build());
              }))
              .uri(backendUri))
          .build();
    }
}

ここで、動的部分は、置換文字列に追加された単なるランダムな数値です。 実際のアプリケーションにはより複雑なロジックが含まれている場合がありますが、基本的なメカニズムは示されているものと同じです。

このコードが実行した手順に関するいくつかの注意事項:最初に、 ServerWebExchangeUtilsクラスからのaddOriginalRequestUrl()、を呼び出して、元のURLを取引所の属性GATEWAY_ORIGINAL_REQUEST_URL_ATTR。 この属性の値は、変更を加える前に受信したURLを追加するリストであり、X-Forwarded-Forヘッダーの処理の一部としてゲートウェイによって内部的に使用されます。

次に、書き換えロジックを適用したら、変更したURLをGATEWAY_REQUEST_URL_ATTR交換の属性に保存する必要があります。 この手順はドキュメントに直接記載されていませんが、カスタムフィルターが他の使用可能なフィルターとうまく機能することを保証します。

5. テスト

書き換えルールをテストするために、標準の JUnit5クラスを少しひねりを加えて使用します。JavaSDKのcom.sun.net.httpserverに基づいて単純なサーバーを起動します。 HttpServerクラス。 サーバーはランダムなポートで起動するため、ポートの競合を回避できます。

ただし、このアプローチの欠点は、実際にサーバーに割り当てられているポートを見つけてSpringに渡す必要があるため、これを使用してルートのuriプロパティを設定できることです。 幸い、Springは、この問題に対する洗練されたソリューションを提供します。 @ DynamicPropertySource。ここでは、これを使用してサーバーを起動し、バインドされたポートの値でプロパティを登録します。

@DynamicPropertySource
static void registerBackendServer(DynamicPropertyRegistry registry) {
    registry.add("rewrite.backend.uri", () -> {
        HttpServer s = startTestServer();
        return "http://localhost:" + s.getAddress().getPort();
    });
}

テストハンドラーは、受信したURIを応答本文にエコーバックするだけです。 これにより、書き換えルールが期待どおりに機能することを確認できます。 たとえば、これは

@Test
void testWhenApiCall_thenRewriteSuccess(@Autowired WebTestClient webClient) {
    webClient.get()
      .uri("http://localhost:" + localPort + "/v1/customer/customer1")
      .exchange()
      .expectBody()
      .consumeWith((result) -> {
          String body = new String(result.getResponseBody());
          assertEquals("/api/customer1", body);
      });
}

6. 結論

このクイックチュートリアルでは、Spring Cloudゲートウェイライブラリを使用してURLを書き換えるさまざまな方法を示しました。 いつものように、すべてのコードはGitHubを介して利用できます。