1前書き


Spring Cloud Netflix Zuul

はhttps://github.com/Netflix/zuul[Netflix Zuul]をラップしたオープンソースのゲートウェイです。それはSpring Bootアプリケーションのためのいくつかの特定の機能を追加します。あいにく、レート制限は箱から出して提供されていません。

  • このチュートリアルでは、レート制限リクエストのサポートを追加したhttps://github.com/marcosbarbero/spring-cloud-zuul-ratelimit[Spring Cloud Zuul RateLimit]を調べます。**


2 Mavenの設定

Spring Cloud Netflix Zuul依存関係に加えて、https://mvnrepository.com/artifact/com.marcosbarbero.cloud/spring-cloud-zuul-ratelimit[Spring Cloud Zuul RateLimit]をアプリケーションの

pom.xml

に追加する必要があります。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

** 3コントローラ例

まず、レート制限を適用するRESTエンドポイントをいくつか作成しましょう。

以下は、2つのエンドポイントを持つ単純なSpring Controllerクラスです。

@Controller
@RequestMapping("/greeting")
public class GreetingController {

    @GetMapping("/simple")
    public ResponseEntity<String> getSimple() {
        return ResponseEntity.ok("Hi!");
    }

    @GetMapping("/advanced")
    public ResponseEntity<String> getAdvanced() {
        return ResponseEntity.ok("Hello, how you doing?");
    }
}

ご覧のとおり、エンドポイントのレート制限に固有のコードはありません。

これは

application.yml

ファイル内のZuulプロパティで設定するためです。そのため、コードを分離したままにします。


4 Zuulのプロパティ

次に、

application.yml

ファイルに次のZuulプロパティを追加しましょう。

zuul:
  routes:
    serviceSimple:
      path:/greeting/simple
      url: forward:/    serviceAdvanced:
      path:/greeting/advanced
      url: forward:/  ratelimit:
    enabled: true
    repository: JPA
    policy-list:
      serviceSimple:
        - limit: 5
          refresh-interval: 60
          type:
            - origin
      serviceAdvanced:
        - limit: 1
          refresh-interval: 2
          type:
            - origin
  strip-prefix: true


zuul.routes

の下に、エンドポイントの詳細を入力します。そして、

zuul.ratelimit.policy-listの下に、エンドポイントのレート制限設定を提供します。

limit

プロパティは、

refresh-interval__内にエンドポイントを呼び出すことができる回数を指定します。

ご覧のとおり、

serviceSimple

エンドポイントには60秒あたり5リクエストというレート制限を追加しました。対照的に、

serviceAdvanced

には2秒間に1リクエストのレート制限があります。


type

設定は、どのレート制限アプローチを採用するかを指定します。可能な値は次のとおりです。


  • origin

    – ユーザーオリジンリクエストに基づくレート制限


  • url

    – ダウンストリームサービスの要求パスに基づくレート制限


  • user

    – 認証されたユーザー名または「匿名」に基づくレート制限

  • 値なし – サービスごとのグローバル構成として機能します。これを使う

アプローチは単にパラメータの ‘type’を設定しない


5レート制限のテスト


5.1. レート制限内でのリクエスト

次に、レート制限をテストしましょう。

@Test
public void whenRequestNotExceedingCapacity__thenReturnOkResponse() {
    ResponseEntity<String> response = restTemplate.getForEntity(SIMPLE__GREETING, String.class);
    assertEquals(OK, response.getStatusCode());

    HttpHeaders headers = response.getHeaders();
    String key = "rate-limit-application__serviceSimple__127.0.0.1";

    assertEquals("5", headers.getFirst(HEADER__LIMIT + key));
    assertEquals("4", headers.getFirst(HEADER__REMAINING + key));
    assertEquals("60000", headers.getFirst(HEADER__RESET + key));
}

ここでは、エンドポイント

/greeting/simple

への呼び出しを1回行います。要求はレート制限内であるため、成功しました。

もう1つの重要な点は、

応答ごとに、レート制限に関する詳細情報を提供するヘッダーが返されることです。

上記の要求では、次のヘッダーが返されます。

X-RateLimit-Limit-rate-limit-application__serviceSimple__127.0.0.1: 5
X-RateLimit-Remaining-rate-limit-application__serviceSimple__127.0.0.1: 4
X-RateLimit-Reset-rate-limit-application__serviceSimple__127.0.0.1: 60000

言い換えると:


  • X-RateLimit-Limit-[key]:

    エンドポイントに設定された

    limit


  • X-RateLimit-Remaining-[key]:

    残りの試行回数

エンドポイントを呼び出す
**

X-RateLimit-Reset-[key]:

残りのミリ秒数(ミリ秒)

エンドポイント用に設定された

refresh-interval

さらに、同じエンドポイントをすぐに再度起動すると、次のようになります。

X-RateLimit-Limit-rate-limit-application__serviceSimple__127.0.0.1: 5
X-RateLimit-Remaining-rate-limit-application__serviceSimple__127.0.0.1: 3
X-RateLimit-Reset-rate-limit-application__serviceSimple__127.0.0.1: 57031

  • 残りの試行回数と残りのミリ秒数が減ったことに注意してください。


5.2. レート制限を超えるリクエスト

制限を超えたときに何が起こるかを見てみましょう。

@Test
public void whenRequestExceedingCapacity__thenReturnTooManyRequestsResponse() throws InterruptedException {
    ResponseEntity<String> response = this.restTemplate.getForEntity(ADVANCED__GREETING, String.class);
    assertEquals(OK, response.getStatusCode());

    for (int i = 0; i < 2; i++) {
        response = this.restTemplate.getForEntity(ADVANCED__GREETING, String.class);
    }

    assertEquals(TOO__MANY__REQUESTS, response.getStatusCode());

    HttpHeaders headers = response.getHeaders();
    String key = "rate-limit-application__serviceAdvanced__127.0.0.1";

    assertEquals("1", headers.getFirst(HEADER__LIMIT + key));
    assertEquals("0", headers.getFirst(HEADER__REMAINING + key));
    assertNotEquals("2000", headers.getFirst(HEADER__RESET + key));

    TimeUnit.SECONDS.sleep(2);

    response = this.restTemplate.getForEntity(ADVANCED__GREETING, String.class);
    assertEquals(OK, response.getStatusCode());
}

ここでは、エンドポイントを

/greeting/advanced

と2回続けて呼び出します。 2秒間に1回のリクエストとしてレート制限を設定しているので、

2番目の呼び出しは失敗します

。その結果、エラーコード

429(

Too Many Requests)


がクライアントに返されます。

以下は、レート制限に達したときに返されるヘッダーです。

X-RateLimit-Limit-rate-limit-application__serviceAdvanced__127.0.0.1: 1
X-RateLimit-Remaining-rate-limit-application__serviceAdvanced__127.0.0.1: 0
X-RateLimit-Reset-rate-limit-application__serviceAdvanced__127.0.0.1: 268

その後、2秒間寝ます。これは、エンドポイントに設定された[更新間隔]です。最後に、もう一度エンドポイントを起動して、成功した応答を受け取ります。


6. カスタムキージェネレータ

  • カスタムキージェネレータを使用してレスポンスヘッダで送信されるキーをカスタマイズすることができます** アプリケーションが

    type

    プロパティによって提供されるオプションを超えてキー戦略を制御する必要があるかもしれないので、これは役に立ちます。

たとえば、これはカスタムの

RateLimitKeyGenerator

実装を作成することによって実行できます。さらに修飾子を追加することも、まったく異なるものを追加することもできます。

@Bean
public RateLimitKeyGenerator rateLimitKeyGenerator(RateLimitProperties properties,
  RateLimitUtils rateLimitUtils) {
    return new DefaultRateLimitKeyGenerator(properties, rateLimitUtils) {
        @Override
        public String key(HttpServletRequest request, Route route,
          RateLimitProperties.Policy policy) {
            return super.key(request, route, policy) + "__" + request.getMethod();
        }
    };
}

上記のコードはRESTメソッド名をキーに追加します。例えば:

X-RateLimit-Limit-rate-limit-application__serviceSimple__127.0.0.1__GET: 5

もう1つ重要な点は、

RateLimitKeyGenerator

Beanが

spring-cloud-zuul-ratelimit

によって自動的に設定されることです。


7. カスタムエラー処理

フレームワークは、レート制限データストレージのさまざまな実装をサポートしています。例えば、Spring Data JPAとRedisが提供されています。デフォルトでは、失敗は

DefaultRateLimiterErrorHandler

クラスを使ってエラーとして記録されます。

エラーを異なる方法で処理する必要がある場合は、カスタムの

RateLimiterErrorHandler

Beanを定義できます。

@Bean
public RateLimiterErrorHandler rateLimitErrorHandler() {
    return new DefaultRateLimiterErrorHandler() {
        @Override
        public void handleSaveError(String key, Exception e) {
           //implementation
        }

        @Override
        public void handleFetchError(String key, Exception e) {
           //implementation
        }

        @Override
        public void handleError(String msg, Exception e) {
           //implementation
        }
    };
}


RateLimitKeyGenerator

Beanと同様、

RateLimiterErrorHandler

Beanも自動的に構成されます。


8結論

この記事では、Spring Cloud Netflix ZuulとSpring Cloud ZuulのRateLimitを使用してAPIをレート制限する方法を見ました。

いつものように、この記事の完全なコードはhttps://github.com/eugenp/tutorials/tree/master/spring-cloud/spring-cloud-zuul[GitHubに載っています]。