Description
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