spring-cloud / spring-cloud/spring-cloud-gateway

Connection pool leaks when response not modified

Open
#4,259 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

waiting-for-triage
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.