RequestRateLimiterGatewayFilterFactory commits 429 response instead of throwing

オープン 初心者向け
#4,175 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@seonwooj0810 がすでに取り組んでいます。

2026年6月8日 から。

  • #4192 @seonwooj0810 による — オープン

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
68/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
静か
技術スタック
java, spring, spring-boot
領域
api, backend

調査の方向性

影響を受けるエントリポイントは RequestRateLimiterGatewayFilterFactory.apply() で、具体的には issue に記載されている allowed=false ブランチです。既存の response-status/setComplete パスと RetryGatewayFilterFactory との相互作用を確認し、提供されている Retry の再現手順を使って、429 の処理が committed 済みのレスポンスを再利用せずに例外ハンドラーへ到達することを検証してください。

索引モデルが issue の本文から書いたものです。

説明

waiting-for-triage

Describe the bug

RequestRateLimiterGatewayFilterFactory calls exchange.getResponse().setComplete() when the rate limit is exceeded.
This commits the response immediately, which has two negative side-effects:

  1. Retry breaks with UnsupportedOperationException: ReadOnlyHttpHeaders.
    When RetryGatewayFilterFactory is placed before the rate limiter and configured with series=CLIENT_ERROR, the retry operator sees the committed 429 status and re-executes the filter chain using the same ServerWebExchange. On the second pass, RequestRateLimiterGatewayFilterFactory attempts to add X-RateLimit-* headers to the already-committed response, causing an immediate UnsupportedOperationException from ReadOnlyHttpHeaders.

  2. Custom exception handlers cannot intercept 429.
    Any WebExceptionHandler or ErrorWebExceptionHandler bean is bypassed because the filter returns a successful Mono<Void> rather than propagating an error.

Regression / Prior art

This was raised in #575 (2018) where @spencergibb suggested the fix:

"I think rather than setting a status we can use ResponseStatusException in Mono.error."

That change was never applied.

Versions

  • Spring Cloud Gateway Server: 4.2.7
  • Spring Boot: 3.5.14
  • Java: 25

Minimal reproduction

Route configuration
spring:
  cloud:
    gateway:
      routes:
        - id: ratelimit-retry-bug
          uri: http://localhost:8081
          predicates:
            - Path=/test/**
          filters:
            # Retry is placed BEFORE rate limiter 
            - name: Retry
              args:
                retries: 3
                series: CLIENT_ERROR
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1
                redis-rate-limiter.requestedTokens: 1

Steps
─────
1. Send a request that exceeds the rate limit.
2. Observe that CustomExceptionHandler is not invoked.
3. If Retry is present with series=CLIENT_ERROR, the application throws:
java.lang.UnsupportedOperationException: null
    at org.springframework.http.ReadOnlyHttpHeaders.add(ReadOnlyHttpHeaders.java:97)
    at org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory.lambda$apply$1(RequestRateLimiterGatewayFilterFactory.java:
112)

Expected behavior
─────────────────
When allowed=false, the filter should throw rather than commit the response, e.g.:

return Mono.error(new ResponseStatusException(config.getStatusCode(), "Rate limit exceeded" ));

This allows:
• ErrorWebExceptionHandler / WebExceptionHandler to customize the 429 response.
• RetryGatewayFilterFactory to decide whether to retry based on the exception (e.g. do NOT retry on ResponseStatusException), avoiding the ReadOnlyHttpHears crash.

Actual behavior
───────────────
The filter calls setResponseStatus(exchange, config.getStatusCode()) followed by exchange.getResponse().setComplete(), which commits the response and returns a successful Mono<Void>.

Suggested fix
─────────────
Replace the final setComplete() branch in RequestRateLimiterGatewayFilterFactory.apply() with Mono.error(new ResponseStatusException(...)), consistent with the suggestion in #575.
主要言語
Java
スター
4.9k
フォーク
3.5k
平均マージ
2日 4時間
マージ済み PR(30日)
9

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

spring-cloud/spring-cloud-gateway のほかの issue

spring-cloud/spring-cloud-gateway の issue をすべて見る

似ている issue

Java の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。