Gateway サービスゲートウェイ

📢 この記事は gemini-2.5-flash によって翻訳されました

Gateway は、全てのマイクロサービスの統一エントリポイントで、以下の機能があるよ。

  • 認証と認可のチェック: ゲートウェイはマイクロサービスのエントリポイントとして、ユーザーがリクエストの資格を持っているか確認する必要があるんだ。もしなければ、インターセプトするよ。
  • リクエストルーティング、ロードバランシング: 全てのリクエストはGatewayを通過する必要があるんだけど、ゲートウェイはビジネスロジックを処理しないんだ。代わりに、特定のルールに基づいてリクエストを特定のマイクロサービスに転送するよ。このプロセスをルーティングって呼ぶんだ。もちろん、ルーティング先のサービスが複数ある場合は、ロードバランシングも必要になるね。
  • リクエストレートリミット: リクエストトラフィックが高すぎる場合、ゲートウェイでダウンストリームのマイクロサービスが受け入れられる速度に合わせてリクエストを許可し、サービスの負荷が過大になるのを防ぐよ。

SpringCloud のゲートウェイ実装は2種類あるんだ。

  • Zuul: Servlet ベースの実装で、ブロッキングプログラミングに属するよ。
  • SpringCloudGateway: Spring5 で提供される WebFlux ベースで、リアクティブプログラミングの実装に属し、より良いパフォーマンスを持ってるんだ。

簡単入門

プロジェクトを作って、依存関係を追加しよう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!--ゲートウェイ-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacosサービスディスカバリ依存-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

基本設定とルーティングルールを書こう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
server:
  port: 10010 # ゲートウェイポート
spring:
  application:
    name: gateway # サービス名
  cloud:
    nacos:
      server-addr: localhost:8848 # nacosアドレス
    gateway:
      routes: # ゲートウェイルーティング設定
        - id: user-service # ルーティングID、カスタムで、ユニークならOK
          # uri: http://127.0.0.1:8081 # ルーティングのターゲットアドレス httpは固定アドレス
          uri: lb://userService # ルーティングのターゲットアドレス lbはロードバランシング、その後にサービス名
          predicates: # ルーティングアサーション、つまりリクエストがルーティングルールに合致するかの条件を判断するよ
            - Path=/user/** # これはパスによるマッチングで、/user/ で始まっていればOK

そしたら <localhost:10010/user/1> にアクセスしてテストしてみてね。

ルーティング設定には、以下が含まれるよ。

  1. ルーティングID: ルーティングの一意な識別子
  2. ルーティングターゲット (uri): ルーティングのターゲットアドレス。http は固定アドレスを、lb はサービス名に基づくロードバランシングを表すよ。
  3. ルーティングアサーション (predicates): ルーティングのルールを判断するもので、合致すればルーティング先に転送されるよ。
  4. ルーティングフィルター (filters): リクエストやレスポンスを処理するよ。

アサーションファクトリ

設定ファイルに書いたアサーションルールはただの文字列で、これらの文字列は Predicate Factory によって読み込まれて処理され、ルーティング判断の条件に変換されるんだ。

例えば、上記の例の Path=/user/** はパスによるマッチングで、このルールは org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory クラスで処理されるよ。似たようなアサーションファクトリにはこんなのがあるんだ。

名前説明
After特定の時刻以降のリクエスト- After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before特定の時刻以前のリクエスト- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between2つの時刻の間のリクエスト- Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
Cookie特定のクッキーを含むリクエスト- Cookie=chocolate, ch.p
Header特定のヘッダーを含むリクエスト- Header=X-Request-Id, \d+
Host特定のホスト(ドメイン)へのリクエスト- Host=.somehost.org,.anotherhost.org
Method指定されたリクエストメソッド- Method=GET,POST
Path指定されたルールに合致するリクエストパス- Path=/red/{segment},/blue/**
Query指定されたパラメータを含むリクエスト- Query=name, Jackまたは- Query=name
RemoteAddr指定された範囲内のリクエスト元IP- RemoteAddr=192.168.1.1/24
Weight重み付け処理

フィルターファクトリ

GatewayFilter は、ゲートウェイで提供される一種のフィルターで、ゲートウェイに入ってくるリクエストやマイクロサービスから返されるレスポンスを処理できるよ。

Spring は31種類の異なるルートフィルターファクトリを提供しているんだ。例えばこんな感じ。

名前説明
AddRequestHeader現在のリクエストにリクエストヘッダーを追加する
RemoveRequestHeaderリクエストからリクエストヘッダーを削除する
AddResponseHeaderレスポンス結果にレスポンスヘッダーを追加する
RemoveResponseHeaderレスポンス結果からレスポンスヘッダーを削除する
RequestRateLimiterリクエストのトラフィックを制限する

詳しい情報はここを参考にしてね: https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/gatewayfilter-factories.html

リクエストヘッダーフィルター

リクエストヘッダーフィルターを例にとると、userService に入る全てのリクエストに ‘Hello=World’ というリクエストヘッダーを追加するよ。

gateway の application.yml を修正して、ルーティングフィルターを追加するんだ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://userservice 
        predicates: 
        - Path=/user/** 
        filters: # フィルター
        - AddRequestHeader=Hello, World # リクエストヘッダーを追加

そしたら Controller を修正してテストできるよ。

1
2
3
4
5
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id, @RequestHeader("Hello") String hello) {
    System.out.println(hello);
    return userService.queryById(id);
}

デフォルトフィルター

もし全てのルートに効果を適用したいなら、フィルターファクトリを default の下に書けばいいよ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://userservice 
        predicates: 
        - Path=/user/**
      default-filters: # デフォルトフィルター項目
      - AddRequestHeader=Hello, World

グローバルフィルター

ゲートウェイには31種類のフィルターが提供されているけど、それぞれのフィルターの役割は固定されてるんだ。もしリクエストをインターセプトして、独自のビジネスロジックを実装したい場合は、そのままじゃ実現できないんだよね。

一方、グローバルフィルターもゲートウェイに入る全てのリクエストとマイクロサービスのレスポンスを処理する役割があって、GatewayFilter と同じだよ。違いは、GatewayFilter が設定を通じて定義され、処理ロジックが固定されているのに対して、GlobalFilter のロジックは自分でコードを書いて実装する必要があるってこと。GlobalFilter インターフェースを実装することで定義するんだ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public interface GlobalFilter {
    /**
     *  現在のリクエストを処理し、必要であれば {@link GatewayFilterChain} を通じて次のフィルターにリクエストを渡す
     *
     * @param exchange リクエストコンテキスト。ここからRequestやResponseなどの情報を取得できる
     * @param chain リクエストを次のフィルターに委譲するために使う 
     * @return {@code Mono<Void>} 現在のフィルターのビジネス処理が終了したことを示す
     */
    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

filter でカスタムロジックを書くことで、以下の機能を実装できるよ。

  • ログイン状態の判断
  • 権限のチェック
  • リクエストレートリミットなど

簡単な使い方

目標:グローバルフィルターを定義して、リクエストをインターセプトし、リクエストのパラメータが以下の条件を満たしているか判断するよ。

  • パラメータに authorization が含まれているか
  • authorization パラメータの値が admin であるか

もし両方満たしていれば通過させて、そうでなければインターセプトするよ。

まず gateway でフィルターを定義するよ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Order(-1) // 優先的に処理
@Component
public class AuthorizeFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // リクエストパラメータを取得
        MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
        // AuthorizeFilterパラメータを取得、ここでは最初のものが認証パラメータと約束
        String authorize = params.getFirst("AuthorizeFilter");
        // チェック
        if("admin".equals(authorize)){
            // 通過
            return chain.filter(exchange);
        }
        // インターセプト、アクセス禁止
        exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
        // 処理終了
        return exchange.getResponse().setComplete();
    }
}

フィルターの実行順序

リクエストがゲートウェイに入ると、3種類のフィルターに出会うよ。現在のルートのフィルター、DefaultFilter、GlobalFilterだね。

リクエストがルーティングされた後、現在のルートフィルターと DefaultFilter、GlobalFilter は1つのフィルターチェーン(コレクション)にマージされて、ソートされた後に各フィルターが順番に実行されるんだ。

各フィルターには int 型の order 値があって、order 値が小さいほど優先度が高く、実行順序も前になるよ。

ルートフィルターと defaultFilter の order 値は Spring によって指定されて、デフォルトでは1からインクリメントされるんだ。

GlobalFilter は Ordered インターフェースを実装するか、または @Order アノテーションを追加することで order 値を指定できるよ。これは自分たちで指定するんだ。

フィルターの order 値が同じ場合、defaultFilter > ルートフィルター > GlobalFilter の順序で実行されるよ。

その中でインターフェースを実装して order 値を指定する方法はこれだね。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//@Order(-1) // 優先的に処理
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // リクエストパラメータを取得
        MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
        // AuthorizeFilterパラメータを取得、ここでは最初のものが認証パラメータと約束
        String authorize = params.getFirst("AuthorizeFilter");
        // チェック
        if("admin".equals(authorize)){
            // 通過
            return chain.filter(exchange);
        }
        // インターセプト、アクセス禁止
        exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
        // 処理終了
        return exchange.getResponse().setComplete();
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

クロスドメイン問題

同一オリジンとは、プロトコルが同じ、IPまたはドメインが同じ、ポートが同じことを指すよ。

クロスドメイン問題:ブラウザがリクエストの発信元がサーバーとクロスドメインのAjaxリクエストを発生させるのを禁止し、リクエストがブラウザによってインターセプトされる問題のこと。その解決策は CORS だよ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
spring:
  cloud:
    gateway:
      globalcors: # グローバルなクロスドメイン処理
        add-to-simple-url-handler-mapping: true # optionsリクエストがインターセプトされる問題を解決
        corsConfigurations:
          '[/**]':
            allowedOrigins: # どのサイトからのクロスドメインリクエストを許可するか 
              - "http://localhost:8090"
            allowedMethods: # 許可されるクロスドメインAjaxのリクエストメソッド
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # リクエストに含めることが許可されるヘッダー情報
            allowCredentials: true # クッキーの持ち運びを許可するかどうか
            maxAge: 360000 # このクロスドメイン検出の有効期間

Visits Since 2025-02-28

Hugo で構築されています。 | テーマ StackJimmy によって設計されています。