spring-cloud / spring-cloud/spring-cloud-gateway
Connection pool leaks when response not modified
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 4.9k
- Forks
- 3.5k
- Avg merge
- 20h 57m
- Merged PRs (30d)
- 8
Description
This bug was reproduced using Spring Boot 4.1.0, Spring Cloud 2025.1.1, Spring Cloud Gateway Server MVC and Apache HttpComponents Client 5.
We observe that some requests are not properly closed, and therefore not returned to the PoolingHttpClientConnectionManager.
After some debug and correlation, we found that the root cause of the issue is in the org.springframework.cloud.gateway.server.mvc.handler.AbstractGatewayServerResponse#writeTo function.
The problem is in this check (a short circuit logic for unmodified responses):
if (SAFE_METHODS.contains(httpMethod)
&& servletWebRequest.checkNotModified(headers().getETag(), lastModified)) {
return null;
}
else {
return writeToInternal(request, response, context);
}
If the response does not meet the criteria for a Not Modified response (the else branch), it calls writeToInternal, which, among other things, copies the response body, and critically, closes the upstream client response (this happens inside RestClientProxyExchange, where the try-with-resources construct closes the client response. Closing the upstream client response is necessary to return the connection to the pool.
However, if the response meets the criteria for a Not Modified response, then an HTTP 304 Not Modified status is set by the checkNotModified call, and the upstream response is ignored. The bug happens because this branch does not properly close the client response.
We were able to reproduce this with Apache HttpComponents Client 5 (the default choice when this library is present in the classpath). Forcing the use of the JDK HttpClient we were unable to reproduce this issue. I believe that the JDK HttpClient responses are not closeable, and I am not sure how the pooling works with this client.
We did not test for Spring Cloud Gateway Server Webflux.
So a quick workaround is to set:
spring:
http:
clients:
imperative:
factory: jdk
Should anyone desire to keep using the Apache HttpComponents Client 5, the workaround below should also work (we verified it works for us). It simply searches for any client response that may be have been used to handle a proxy call, and closes them.
@Bean
public WebMvcConfigurer cleanupWebMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
Object clientResponse = request.getAttribute(MvcUtils.CLIENT_RESPONSE_ATTR);
if (clientResponse instanceof ClientHttpResponse clientHttpResponse) {
clientHttpResponse.close();
}
}
});
}
};
}
We believe that the short circuit logic in AbstractGatewayServerResponse#writeTo needs to close the client response.
Ideally, this logic should be optional, possibly controllable by a configuration property. The default could be to enable it, to remain a non-breaking change. Generally, we prefer the backend endpoints to have authority over the determination if a response needs to be generated or not, and the gateway proxy to have minimal intervention on those aspects.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in AbstractGatewayServerResponse.java at writeTo and trace the normal response path through RestClientProxyExchange.java. Reproduce the 304 Not Modified path with Apache HttpComponents Client 5 and verify that the upstream client response is closed and the connection returns to the pool, while normal proxy responses remain unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring, spring-boot
- Domain
- api, backend, networking
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100