Skip to content

Exception with RequestRateLimiter filter combined with response header filters #3718

Open
@LorenzoLuconi

Description

@LorenzoLuconi

I found a problem using the RequestRateLimiter filter in combination with those that alter the response headers such as RemoveResponseHeader.

I'm using Spring Cloud Gateway 4.2.0.

When RedisRateLimiter blocks a request, for limit exceeds, we get an exception (and 500 HTTP response code):

2025-03-13T12:21:38.717+01:00 ERROR 47695 --- [ctor-http-nio-2] o.s.w.s.adapter.HttpWebHandlerAdapter    : [53f4cb46-1] Error [java.lang.UnsupportedOperationException] for HTTP GET "/test", but ServerHttpResponse already committed (403 FORBIDDEN)
2025-03-13T12:21:38.718+01:00 ERROR 47695 --- [ctor-http-nio-2] r.n.http.server.HttpServerOperations     : [53f4cb46-1, L:/[0:0:0:0:0:0:0:1]:8080 - R:/[0:0:0:0:0:0:0:1]:52058] Error starting response. Replying error status

java.lang.UnsupportedOperationException: null
	at org.springframework.http.ReadOnlyHttpHeaders.remove(ReadOnlyHttpHeaders.java:142) ~[spring-web-6.2.3.jar:6.2.3]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	*__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ HTTP GET "/test" [ExceptionHandlingWebHandler]
Original Stack Trace:
		at org.springframework.http.ReadOnlyHttpHeaders.remove(ReadOnlyHttpHeaders.java:142) ~[spring-web-6.2.3.jar:6.2.3]
		at org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory$1.lambda$filter$0(RemoveResponseHeaderGatewayFilterFactory.java:51) ~[spring-cloud-gateway-server-4.2.0.jar:4.2.0]
		....

This is my configuration:

spring:
  cloud:
    gateway:
      routes:
        - id: MY_ROUTE
          predicates:
            - Path=/test
          filters:
            - SetPath=/anything
            - RemoveResponseHeader=Server
            - name: RequestRateLimiter
              args:
                # key-resolver: "#{@myKeyResolver}"
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1
                redis-rate-limiter.requestedTokens: 1
          uri: https://httpbin.org

To simplify the test, I omitted the definition of a KeyResolver so that the RequestRateLimiter filter always blocks the request (“EMPTY_KEY”) with HTTP code 403.

I think the issue occurs because RequestRateLimiter sets the response as completed (when limit exceeds) and then you can no longer change the HTTP headers.
The only workaround I found is to create my own version of some basic filters so that they perform a check if the response has already been completed, for example my version of the RemoveResponseHeader:

// ....
  @Override
    public GatewayFilter apply(NameConfig config) {
        return (exchange, chain) -> chain.filter(exchange)
                .then(Mono.fromRunnable(() -> {
                    if ( ! exchange.getResponse().isCommitted() )
                        exchange.getResponse().getHeaders().remove(config.getName());
                }));
    }

If this is the solution, maybe a similar check should be added to all filters included in the project that may have this issue.

If there are any other solutions please let me know.

You can see a full example here: https://github.com/LorenzoLuconi/spring-cloud-gateway-exception-exmple

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions