Apply cookie handling to reactive HttpComponents connector

The spring.http.clients.cookie-handling property and
HttpClientSettings.cookieHandling() were honored by every imperative
ClientHttpRequestFactoryBuilder and by the Jetty, JDK and Reactor
ClientHttpConnectorBuilders, but HttpComponentsHttpAsyncClientBuilder
ignored the setting. As a result, a WebClient backed by Apache
HttpComponents kept storing cookies even when cookie handling was
disabled.

Map the setting to the default request config's cookie spec, as
HttpComponentsHttpClientBuilder already does, and document the property
alongside the other global HTTP client settings.

See gh-51724

Signed-off-by: Hyun Lee <dlwhdugs4147@gmail.com>
This commit is contained in:
Hyun Lee
2026-09-15 13:22:25 +01:00
committed by Andy Wilkinson
parent a8f1a99f7d
commit fa8ecccc37
4 changed files with 49 additions and 5 deletions
@@ -26,6 +26,7 @@ import org.apache.hc.client5.http.SystemDefaultDnsResolver;
import org.apache.hc.client5.http.async.HttpAsyncClient;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.StandardCookieSpec;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
@@ -178,7 +179,7 @@ public final class HttpComponentsHttpAsyncClientBuilder {
.useSystemProperties()
.setRedirectStrategy(HttpComponentsRedirectStrategy.get(settings.redirects()))
.setConnectionManager(createConnectionManager(settings))
.setDefaultRequestConfig(createDefaultRequestConfig());
.setDefaultRequestConfig(createDefaultRequestConfig(settings));
this.customizer.accept(builder);
return builder.build();
}
@@ -211,8 +212,15 @@ public final class HttpComponentsHttpAsyncClientBuilder {
return builder.build();
}
private RequestConfig createDefaultRequestConfig() {
private RequestConfig createDefaultRequestConfig(HttpClientSettings settings) {
RequestConfig.Builder builder = RequestConfig.custom();
if (settings.cookieHandling() != null) {
String cookieSpec = switch (settings.cookieHandling()) {
case ENABLE_WHEN_POSSIBLE, ENABLE -> StandardCookieSpec.STRICT;
case DISABLE -> StandardCookieSpec.IGNORE;
};
builder.setCookieSpec(cookieSpec);
}
this.defaultRequestConfigCustomizer.accept(builder);
return builder.build();
}
@@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.http.client.HttpClientSettings;
import org.springframework.boot.http.client.HttpCookieHandling;
import org.springframework.boot.http.client.HttpRedirects;
import org.springframework.boot.http.client.InetAddressFilter;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -52,9 +53,10 @@ class HttpClientAutoConfigurationTests {
void createsHttpClientSettingsFromProperties() {
this.contextRunner
.withPropertyValues("spring.http.clients.redirects=dont-follow", "spring.http.clients.connect-timeout=1s",
"spring.http.clients.read-timeout=2s")
.run((context) -> assertThat(context.getBean(HttpClientSettings.class)).isEqualTo(new HttpClientSettings(
null, HttpRedirects.DONT_FOLLOW, Duration.ofSeconds(1), Duration.ofSeconds(2), null)));
"spring.http.clients.read-timeout=2s", "spring.http.clients.cookie-handling=disable")
.run((context) -> assertThat(context.getBean(HttpClientSettings.class))
.isEqualTo(new HttpClientSettings(HttpCookieHandling.DISABLE, HttpRedirects.DONT_FOLLOW,
Duration.ofSeconds(1), Duration.ofSeconds(2), null)));
}
@Test
@@ -25,15 +25,19 @@ import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.async.HttpAsyncClient;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.StandardCookieSpec;
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
import org.apache.hc.core5.function.Resolver;
import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.springframework.boot.http.client.HttpClientSettings;
import org.springframework.boot.http.client.HttpComponentsHttpAsyncClientBuilder;
import org.springframework.boot.http.client.HttpCookieHandling;
import org.springframework.boot.http.client.InetAddressFilter;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources;
@@ -129,6 +133,28 @@ class HttpComponentsClientHttpConnectorBuilderTests
.isSameAs(dnsResolver);
}
@Test
void defaultCookieHandling() {
HttpComponentsClientHttpConnector connector = ClientHttpConnectorBuilder.httpComponents()
.build(HttpClientSettings.defaults());
assertThat(connector).extracting("client.defaultConfig.cookieSpec").isNull();
}
@Test
void cookieHandlingDisabled() {
HttpComponentsClientHttpConnector connector = ClientHttpConnectorBuilder.httpComponents()
.build(HttpClientSettings.defaults().withCookieHandling(HttpCookieHandling.DISABLE));
assertThat(connector).extracting("client.defaultConfig.cookieSpec").isEqualTo(StandardCookieSpec.IGNORE);
}
@ParameterizedTest
@EnumSource(names = { "ENABLE", "ENABLE_WHEN_POSSIBLE" })
void cookieHandlingEnabled(HttpCookieHandling cookieHandling) {
HttpComponentsClientHttpConnector connector = ClientHttpConnectorBuilder.httpComponents()
.build(HttpClientSettings.defaults().withCookieHandling(cookieHandling));
assertThat(connector).extracting("client.defaultConfig.cookieSpec").isEqualTo(StandardCookieSpec.STRICT);
}
@Override
protected long connectTimeout(HttpComponentsClientHttpConnector connector) {
return getConnectorConfig(connector).getConnectTimeout().toMilliseconds();