mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-10-03 07:49:03 +00:00
Merge pull request #49261 from apoorvdarshan
* gh-49261: Polish "Update TestRestTemplate's default cookie handling to match RestTemplate's" Update TestRestTemplate's default cookie handling to match RestTemplate's Closes gh-49261
This commit is contained in:
-1
@@ -59,7 +59,6 @@ If you are using Spring WebFlux, consider the javadoc:org.springframework.test.w
|
||||
|
||||
It is recommended, but not mandatory, to use the Apache HTTP Client (version 5.1 or better).
|
||||
If you have that on your classpath, the javadoc:org.springframework.boot.resttestclient.TestRestTemplate[] responds by configuring the client appropriately.
|
||||
If you do use Apache's HTTP client it is configured to ignore cookies (so the template is stateless).
|
||||
|
||||
javadoc:org.springframework.boot.resttestclient.TestRestTemplate[] can be instantiated directly in your integration tests, as shown in the following example:
|
||||
|
||||
|
||||
+43
-9
@@ -25,6 +25,8 @@ import org.springframework.boot.ssl.SslBundle;
|
||||
/**
|
||||
* Settings that can be applied when creating an imperative or reactive HTTP client.
|
||||
*
|
||||
* @param cookieHandling the cookie handling strategy to use or null to use the underlying
|
||||
* library's default
|
||||
* @param redirects the follow redirect strategy to use or null to redirect whenever the
|
||||
* underlying library allows it
|
||||
* @param connectTimeout the connect timeout
|
||||
@@ -33,10 +35,37 @@ import org.springframework.boot.ssl.SslBundle;
|
||||
* @author Phillip Webb
|
||||
* @since 3.5.0
|
||||
*/
|
||||
public record HttpClientSettings(@Nullable HttpRedirects redirects, @Nullable Duration connectTimeout,
|
||||
@Nullable Duration readTimeout, @Nullable SslBundle sslBundle) {
|
||||
public record HttpClientSettings(@Nullable HttpCookieHandling cookieHandling, @Nullable HttpRedirects redirects,
|
||||
@Nullable Duration connectTimeout, @Nullable Duration readTimeout, @Nullable SslBundle sslBundle) {
|
||||
|
||||
private static final HttpClientSettings defaults = new HttpClientSettings(null, null, null, null);
|
||||
/**
|
||||
* Create a new {@link HttpClientSettings} instance.
|
||||
* @param redirects the follow redirect strategy to use
|
||||
* @param connectTimeout the connect timeout
|
||||
* @param readTimeout the read timeout
|
||||
* @param sslBundle the SSL bundle providing SSL configuration
|
||||
* @deprecated since 4.1.0 for removal in 4.3.0 in favor of
|
||||
* {@link HttpClientSettings#HttpClientSettings(HttpCookieHandling, HttpRedirects, Duration, Duration, SslBundle)}
|
||||
*/
|
||||
@Deprecated(since = "4.1.0", forRemoval = true)
|
||||
public HttpClientSettings(@Nullable HttpRedirects redirects, @Nullable Duration connectTimeout,
|
||||
@Nullable Duration readTimeout, @Nullable SslBundle sslBundle) {
|
||||
this(null, redirects, connectTimeout, readTimeout, sslBundle);
|
||||
}
|
||||
|
||||
private static final HttpClientSettings defaults = new HttpClientSettings(null, null, null, null, null);
|
||||
|
||||
/**
|
||||
* Return a new {@link HttpClientSettings} instance with an updated cookie handling
|
||||
* setting.
|
||||
* @param cookieHandling the new cookie handling setting
|
||||
* @return a new {@link HttpClientSettings} instance
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public HttpClientSettings withCookieHandling(@Nullable HttpCookieHandling cookieHandling) {
|
||||
return new HttpClientSettings(cookieHandling, this.redirects, this.connectTimeout, this.readTimeout,
|
||||
this.sslBundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link HttpClientSettings} instance with an updated connect timeout
|
||||
@@ -46,7 +75,8 @@ public record HttpClientSettings(@Nullable HttpRedirects redirects, @Nullable Du
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public HttpClientSettings withConnectTimeout(@Nullable Duration connectTimeout) {
|
||||
return new HttpClientSettings(this.redirects, connectTimeout, this.readTimeout, this.sslBundle);
|
||||
return new HttpClientSettings(this.cookieHandling, this.redirects, connectTimeout, this.readTimeout,
|
||||
this.sslBundle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +87,8 @@ public record HttpClientSettings(@Nullable HttpRedirects redirects, @Nullable Du
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public HttpClientSettings withReadTimeout(@Nullable Duration readTimeout) {
|
||||
return new HttpClientSettings(this.redirects, this.connectTimeout, readTimeout, this.sslBundle);
|
||||
return new HttpClientSettings(this.cookieHandling, this.redirects, this.connectTimeout, readTimeout,
|
||||
this.sslBundle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +100,7 @@ public record HttpClientSettings(@Nullable HttpRedirects redirects, @Nullable Du
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public HttpClientSettings withTimeouts(@Nullable Duration connectTimeout, @Nullable Duration readTimeout) {
|
||||
return new HttpClientSettings(this.redirects, connectTimeout, readTimeout, this.sslBundle);
|
||||
return new HttpClientSettings(this.cookieHandling, this.redirects, connectTimeout, readTimeout, this.sslBundle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,7 +111,8 @@ public record HttpClientSettings(@Nullable HttpRedirects redirects, @Nullable Du
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public HttpClientSettings withSslBundle(@Nullable SslBundle sslBundle) {
|
||||
return new HttpClientSettings(this.redirects, this.connectTimeout, this.readTimeout, sslBundle);
|
||||
return new HttpClientSettings(this.cookieHandling, this.redirects, this.connectTimeout, this.readTimeout,
|
||||
sslBundle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +122,8 @@ public record HttpClientSettings(@Nullable HttpRedirects redirects, @Nullable Du
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public HttpClientSettings withRedirects(@Nullable HttpRedirects redirects) {
|
||||
return new HttpClientSettings(redirects, this.connectTimeout, this.readTimeout, this.sslBundle);
|
||||
return new HttpClientSettings(this.cookieHandling, redirects, this.connectTimeout, this.readTimeout,
|
||||
this.sslBundle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,11 +137,12 @@ public record HttpClientSettings(@Nullable HttpRedirects redirects, @Nullable Du
|
||||
if (other == null) {
|
||||
return this;
|
||||
}
|
||||
HttpCookieHandling cookieHandling = (cookieHandling() != null) ? cookieHandling() : other.cookieHandling();
|
||||
HttpRedirects redirects = (redirects() != null) ? redirects() : other.redirects();
|
||||
Duration connectTimeout = (connectTimeout() != null) ? connectTimeout() : other.connectTimeout();
|
||||
Duration readTimeout = (readTimeout() != null) ? readTimeout() : other.readTimeout();
|
||||
SslBundle sslBundle = (sslBundle() != null) ? sslBundle() : other.sslBundle();
|
||||
return new HttpClientSettings(redirects, connectTimeout, readTimeout, sslBundle);
|
||||
return new HttpClientSettings(cookieHandling, redirects, connectTimeout, readTimeout, sslBundle);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+10
-2
@@ -23,6 +23,7 @@ import java.util.function.Consumer;
|
||||
|
||||
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.classic.CloseableHttpClient;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
|
||||
@@ -180,7 +181,7 @@ public final class HttpComponentsHttpClientBuilder {
|
||||
.useSystemProperties()
|
||||
.setRedirectStrategy(HttpComponentsRedirectStrategy.get(settings.redirects()))
|
||||
.setConnectionManager(createConnectionManager(settings))
|
||||
.setDefaultRequestConfig(createDefaultRequestConfig());
|
||||
.setDefaultRequestConfig(createDefaultRequestConfig(settings));
|
||||
this.customizer.accept(builder);
|
||||
return builder.build();
|
||||
}
|
||||
@@ -218,8 +219,15 @@ public final class HttpComponentsHttpClientBuilder {
|
||||
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();
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2012-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.http.client;
|
||||
|
||||
/**
|
||||
* Cookie handling strategies supported by HTTP clients.
|
||||
*
|
||||
* @author Apoorv Darshan
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public enum HttpCookieHandling {
|
||||
|
||||
/**
|
||||
* Enable cookie handling (if the underlying library has support). When enabled,
|
||||
* cookies received in a response are stored and automatically included in subsequent
|
||||
* matching requests.
|
||||
*/
|
||||
ENABLE_WHEN_POSSIBLE,
|
||||
|
||||
/**
|
||||
* Enable cookie handling (fail if the underlying library has no support). When
|
||||
* enabled, cookies received in a response are stored and automatically included in
|
||||
* subsequent matching requests.
|
||||
*/
|
||||
ENABLE,
|
||||
|
||||
/**
|
||||
* Disable cookie handling (fail if the underlying library has no support).
|
||||
*/
|
||||
DISABLE
|
||||
|
||||
}
|
||||
+10
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.boot.http.client;
|
||||
|
||||
import java.net.CookieHandler;
|
||||
import java.net.CookieManager;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpClient.Redirect;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -83,6 +85,7 @@ public final class JdkHttpClientBuilder {
|
||||
Assert.isTrue(settings.readTimeout() == null, "'settings' must not have a 'readTimeout'");
|
||||
HttpClient.Builder builder = HttpClient.newBuilder();
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
map.from(settings::cookieHandling).as(this::asCookieHandler).to(builder::cookieHandler);
|
||||
map.from(settings::redirects).always().as(this::asHttpClientRedirect).to(builder::followRedirects);
|
||||
map.from(settings::connectTimeout).to(builder::connectTimeout);
|
||||
map.from(settings::sslBundle).as(SslBundle::createSslContext).to(builder::sslContext);
|
||||
@@ -99,6 +102,13 @@ public final class JdkHttpClientBuilder {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private @Nullable CookieHandler asCookieHandler(HttpCookieHandling cookieHandling) {
|
||||
return switch (cookieHandling) {
|
||||
case ENABLE_WHEN_POSSIBLE, ENABLE -> new CookieManager();
|
||||
case DISABLE -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private Redirect asHttpClientRedirect(@Nullable HttpRedirects redirects) {
|
||||
if (redirects == null) {
|
||||
return Redirect.NORMAL;
|
||||
|
||||
+9
@@ -28,6 +28,7 @@ import org.eclipse.jetty.client.HttpClientTransport;
|
||||
import org.eclipse.jetty.client.Request;
|
||||
import org.eclipse.jetty.client.transport.HttpClientTransportDynamic;
|
||||
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.http.HttpCookieStore;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -140,6 +141,7 @@ public final class JettyHttpClientBuilder {
|
||||
HttpClient httpClient = createHttpClient(settings.readTimeout(), transport);
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
map.from(settings::connectTimeout).as(Duration::toMillis).to(httpClient::setConnectTimeout);
|
||||
map.from(settings::cookieHandling).as(this::asCookieStore).to(httpClient::setHttpCookieStore);
|
||||
map.from(settings::redirects).always().as(this::followRedirects).to(httpClient::setFollowRedirects);
|
||||
this.customizer.accept(httpClient);
|
||||
return httpClient;
|
||||
@@ -182,6 +184,13 @@ public final class JettyHttpClientBuilder {
|
||||
return factory;
|
||||
}
|
||||
|
||||
private @Nullable HttpCookieStore asCookieStore(HttpCookieHandling cookieHandling) {
|
||||
return switch (cookieHandling) {
|
||||
case ENABLE_WHEN_POSSIBLE, ENABLE -> new HttpCookieStore.Default();
|
||||
case DISABLE -> new HttpCookieStore.Empty();
|
||||
};
|
||||
}
|
||||
|
||||
private boolean followRedirects(@Nullable HttpRedirects redirects) {
|
||||
if (redirects == null) {
|
||||
return true;
|
||||
|
||||
+3
@@ -109,6 +109,9 @@ public final class ReactorHttpClientBuilder {
|
||||
.orFrom(() -> HttpRedirects.FOLLOW_WHEN_POSSIBLE)
|
||||
.as(this::followRedirects)
|
||||
.to(httpClient, HttpClient::followRedirect);
|
||||
if (HttpCookieHandling.ENABLE.equals(settings.cookieHandling())) {
|
||||
throw new IllegalArgumentException("Reactor Netty HTTP client does not support cookie handling");
|
||||
}
|
||||
httpClient = map.from(settings::sslBundle).to(httpClient, this::secure);
|
||||
return this.customizer.apply(httpClient);
|
||||
}
|
||||
|
||||
+4
@@ -78,6 +78,10 @@ final class ReflectiveComponentsClientHttpRequestFactoryBuilder<T extends Client
|
||||
Assert.state(settings.sslBundle() == null, "Unable to set SSL bundle using reflection");
|
||||
Assert.state(settings.redirects() == null || settings.redirects() == HttpRedirects.FOLLOW_WHEN_POSSIBLE,
|
||||
"Unable to set redirect follow using reflection");
|
||||
Assert.state(
|
||||
settings.cookieHandling() == null
|
||||
|| settings.cookieHandling() == HttpCookieHandling.ENABLE_WHEN_POSSIBLE,
|
||||
"Unable to set HTTP cookie handling using reflection");
|
||||
ClientHttpRequestFactory unwrapped = unwrapRequestFactoryIfNecessary(requestFactory);
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
map.from(settings::connectTimeout).to((connectTimeout) -> setConnectTimeout(unwrapped, connectTimeout));
|
||||
|
||||
+3
@@ -78,6 +78,9 @@ public final class SimpleClientHttpRequestFactoryBuilder
|
||||
|
||||
@Override
|
||||
protected SimpleClientHttpRequestFactory createClientHttpRequestFactory(HttpClientSettings settings) {
|
||||
if (settings.cookieHandling() == HttpCookieHandling.ENABLE) {
|
||||
throw new IllegalArgumentException("Simple HTTP request factory does not support HTTP cookie handling");
|
||||
}
|
||||
SslBundle sslBundle = settings.sslBundle();
|
||||
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpsRequestFactory(settings);
|
||||
Assert.state(sslBundle == null || !sslBundle.getOptions().isSpecified(),
|
||||
|
||||
+14
@@ -21,6 +21,7 @@ import java.time.Duration;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.boot.http.client.HttpClientSettings;
|
||||
import org.springframework.boot.http.client.HttpCookieHandling;
|
||||
import org.springframework.boot.http.client.HttpRedirects;
|
||||
|
||||
/**
|
||||
@@ -47,6 +48,11 @@ public abstract class HttpClientSettingsProperties {
|
||||
*/
|
||||
private @Nullable Duration readTimeout;
|
||||
|
||||
/**
|
||||
* Handling for HTTP cookies.
|
||||
*/
|
||||
private @Nullable HttpCookieHandling cookieHandling;
|
||||
|
||||
/**
|
||||
* Default SSL configuration for a client HTTP request.
|
||||
*/
|
||||
@@ -76,6 +82,14 @@ public abstract class HttpClientSettingsProperties {
|
||||
this.readTimeout = readTimeout;
|
||||
}
|
||||
|
||||
public @Nullable HttpCookieHandling getCookieHandling() {
|
||||
return this.cookieHandling;
|
||||
}
|
||||
|
||||
public void setCookieHandling(@Nullable HttpCookieHandling cookieHandling) {
|
||||
this.cookieHandling = cookieHandling;
|
||||
}
|
||||
|
||||
public Ssl getSsl() {
|
||||
return this.ssl;
|
||||
}
|
||||
|
||||
+1
@@ -49,6 +49,7 @@ public class HttpClientSettingsPropertyMapper {
|
||||
settings = map.from(properties::getRedirects).to(settings, HttpClientSettings::withRedirects);
|
||||
settings = map.from(properties::getConnectTimeout).to(settings, HttpClientSettings::withConnectTimeout);
|
||||
settings = map.from(properties::getReadTimeout).to(settings, HttpClientSettings::withReadTimeout);
|
||||
settings = map.from(properties::getCookieHandling).to(settings, HttpClientSettings::withCookieHandling);
|
||||
settings = map.from(properties::getSsl)
|
||||
.as(HttpClientSettingsProperties.Ssl::getBundle)
|
||||
.as(this::getSslBundle)
|
||||
|
||||
+16
-3
@@ -47,7 +47,8 @@ class HttpClientSettingsTests {
|
||||
|
||||
@Test
|
||||
void createWithNulls() {
|
||||
HttpClientSettings settings = new HttpClientSettings(null, null, null, null);
|
||||
HttpClientSettings settings = new HttpClientSettings(null, null, null, null, null);
|
||||
assertThat(settings.cookieHandling()).isNull();
|
||||
assertThat(settings.redirects()).isNull();
|
||||
assertThat(settings.connectTimeout()).isNull();
|
||||
assertThat(settings.readTimeout()).isNull();
|
||||
@@ -82,6 +83,16 @@ class HttpClientSettingsTests {
|
||||
assertThat(settings.sslBundle()).isSameAs(sslBundle);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withCookieHandlingReturnsInstanceWithUpdatedCookieHandling() {
|
||||
HttpClientSettings settings = HttpClientSettings.defaults().withCookieHandling(HttpCookieHandling.DISABLE);
|
||||
assertThat(settings.cookieHandling()).isEqualTo(HttpCookieHandling.DISABLE);
|
||||
assertThat(settings.redirects()).isNull();
|
||||
assertThat(settings.connectTimeout()).isNull();
|
||||
assertThat(settings.readTimeout()).isNull();
|
||||
assertThat(settings.sslBundle()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void withRedirectsReturnsInstanceWithUpdatedRedirect() {
|
||||
HttpClientSettings settings = HttpClientSettings.defaults().withRedirects(HttpRedirects.DONT_FOLLOW);
|
||||
@@ -94,8 +105,10 @@ class HttpClientSettingsTests {
|
||||
@Test
|
||||
void orElseReturnsNewInstanceWithUpdatedValues() {
|
||||
SslBundle sslBundle = mock(SslBundle.class);
|
||||
HttpClientSettings settings = new HttpClientSettings(null, ONE_SECOND, null, null)
|
||||
.orElse(new HttpClientSettings(HttpRedirects.FOLLOW_WHEN_POSSIBLE, TWO_SECONDS, TWO_SECONDS, sslBundle));
|
||||
HttpClientSettings settings = new HttpClientSettings(null, null, ONE_SECOND, null, null)
|
||||
.orElse(new HttpClientSettings(HttpCookieHandling.ENABLE, HttpRedirects.FOLLOW_WHEN_POSSIBLE, TWO_SECONDS,
|
||||
TWO_SECONDS, sslBundle));
|
||||
assertThat(settings.cookieHandling()).isEqualTo(HttpCookieHandling.ENABLE);
|
||||
assertThat(settings.redirects()).isEqualTo(HttpRedirects.FOLLOW_WHEN_POSSIBLE);
|
||||
assertThat(settings.connectTimeout()).isEqualTo(ONE_SECOND);
|
||||
assertThat(settings.readTimeout()).isEqualTo(TWO_SECONDS);
|
||||
|
||||
+25
@@ -22,12 +22,15 @@ import java.util.List;
|
||||
import org.apache.hc.client5.http.HttpRoute;
|
||||
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.classic.CloseableHttpClient;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
|
||||
import org.apache.hc.core5.function.Resolver;
|
||||
import org.apache.hc.core5.http.io.SocketConfig;
|
||||
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.HttpComponentsHttpClientBuilder.TlsSocketStrategyFactory;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
@@ -102,6 +105,28 @@ class HttpComponentsClientHttpRequestFactoryBuilderTests
|
||||
customizer.assertCalled();
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultCookieHandling() {
|
||||
HttpComponentsClientHttpRequestFactory factory = ClientHttpRequestFactoryBuilder.httpComponents()
|
||||
.build(HttpClientSettings.defaults());
|
||||
assertThat(factory).extracting("httpClient.defaultConfig.cookieSpec").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void cookieHandlingDisabled() {
|
||||
HttpComponentsClientHttpRequestFactory factory = ClientHttpRequestFactoryBuilder.httpComponents()
|
||||
.build(HttpClientSettings.defaults().withCookieHandling(HttpCookieHandling.DISABLE));
|
||||
assertThat(factory).extracting("httpClient.defaultConfig.cookieSpec").isEqualTo(StandardCookieSpec.IGNORE);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(names = { "ENABLE", "ENABLE_WHEN_POSSIBLE" })
|
||||
void cookieHandlingEnabled(HttpCookieHandling cookieHandling) {
|
||||
HttpComponentsClientHttpRequestFactory factory = ClientHttpRequestFactoryBuilder.httpComponents()
|
||||
.build(HttpClientSettings.defaults().withCookieHandling(cookieHandling));
|
||||
assertThat(factory).extracting("httpClient.defaultConfig.cookieSpec").isEqualTo(StandardCookieSpec.STRICT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long connectTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
|
||||
return getConnectorConfig(requestFactory).getConnectTimeout().toMilliseconds();
|
||||
|
||||
+30
@@ -21,6 +21,8 @@ import java.time.Duration;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.http.client.JdkClientHttpRequestFactory;
|
||||
@@ -68,6 +70,34 @@ class JdkClientHttpRequestFactoryBuilderTests
|
||||
customizer.assertCalled();
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultCookieHandling() {
|
||||
JdkClientHttpRequestFactory factory = ClientHttpRequestFactoryBuilder.jdk()
|
||||
.build(HttpClientSettings.defaults());
|
||||
HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(factory, "httpClient");
|
||||
assertThat(httpClient).isNotNull();
|
||||
assertThat(httpClient.cookieHandler()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void cookieHandlingDisabled() {
|
||||
JdkClientHttpRequestFactory factory = ClientHttpRequestFactoryBuilder.jdk()
|
||||
.build(HttpClientSettings.defaults().withCookieHandling(HttpCookieHandling.DISABLE));
|
||||
HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(factory, "httpClient");
|
||||
assertThat(httpClient).isNotNull();
|
||||
assertThat(httpClient.cookieHandler()).isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(names = { "ENABLE", "ENABLE_WHEN_POSSIBLE" })
|
||||
void cookieHandlingEnabled(HttpCookieHandling cookieHandling) {
|
||||
JdkClientHttpRequestFactory factory = ClientHttpRequestFactoryBuilder.jdk()
|
||||
.build(HttpClientSettings.defaults().withCookieHandling(cookieHandling));
|
||||
HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(factory, "httpClient");
|
||||
assertThat(httpClient).isNotNull();
|
||||
assertThat(httpClient.cookieHandler()).isNotEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long connectTimeout(JdkClientHttpRequestFactory requestFactory) {
|
||||
HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient");
|
||||
|
||||
+25
@@ -19,8 +19,11 @@ package org.springframework.boot.http.client;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpClientTransport;
|
||||
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.http.HttpCookieStore;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
|
||||
import org.springframework.http.client.JettyClientHttpRequestFactory;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
@@ -75,6 +78,28 @@ class JettyClientHttpRequestFactoryBuilderTests
|
||||
.isInstanceOf(TestHttpClientTransport.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultCookieHandling() {
|
||||
JettyClientHttpRequestFactory factory = ClientHttpRequestFactoryBuilder.jetty()
|
||||
.build(HttpClientSettings.defaults());
|
||||
assertThat(factory).extracting("httpClient.cookieStore").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void cookieHandlingDisabled() {
|
||||
JettyClientHttpRequestFactory factory = ClientHttpRequestFactoryBuilder.jetty()
|
||||
.build(HttpClientSettings.defaults().withCookieHandling(HttpCookieHandling.DISABLE));
|
||||
assertThat(factory).extracting("httpClient.cookieStore").isInstanceOf(HttpCookieStore.Empty.class);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(names = { "ENABLE", "ENABLE_WHEN_POSSIBLE" })
|
||||
void cookieHandlingEnabled(HttpCookieHandling cookieHandling) {
|
||||
JettyClientHttpRequestFactory factory = ClientHttpRequestFactoryBuilder.jetty()
|
||||
.build(HttpClientSettings.defaults().withCookieHandling(cookieHandling));
|
||||
assertThat(factory).extracting("httpClient.cookieStore").isInstanceOf(HttpCookieStore.Default.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long connectTimeout(JettyClientHttpRequestFactory requestFactory) {
|
||||
HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient");
|
||||
|
||||
+17
@@ -24,6 +24,8 @@ import java.util.function.UnaryOperator;
|
||||
|
||||
import io.netty.channel.ChannelOption;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
|
||||
import org.springframework.http.client.ReactorClientHttpRequestFactory;
|
||||
@@ -31,6 +33,8 @@ import org.springframework.http.client.ReactorResourceFactory;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
@@ -98,6 +102,19 @@ class ReactorClientHttpRequestFactoryBuilderTests
|
||||
assertThat(called).containsExactly(true);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(names = { "DISABLE", "ENABLE_WHEN_POSSIBLE" })
|
||||
void doesNotThrowWhenCookieHandlingNotEnabled(HttpCookieHandling cookieHandling) {
|
||||
assertThatNoException().isThrownBy(() -> ClientHttpRequestFactoryBuilder.reactor()
|
||||
.build(HttpClientSettings.defaults().withCookieHandling(cookieHandling)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwsWhenCookieHandlingEnabled() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> ClientHttpRequestFactoryBuilder.reactor()
|
||||
.build(HttpClientSettings.defaults().withCookieHandling(HttpCookieHandling.ENABLE)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long connectTimeout(ReactorClientHttpRequestFactory requestFactory) {
|
||||
HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient");
|
||||
|
||||
+14
@@ -67,6 +67,20 @@ class ReflectiveComponentsClientHttpRequestFactoryBuilderTests
|
||||
.withMessage("Unable to set redirect follow using reflection");
|
||||
}
|
||||
|
||||
@Test
|
||||
void cookieHandlingEnable() {
|
||||
HttpClientSettings settings = HttpClientSettings.defaults().withCookieHandling(HttpCookieHandling.ENABLE);
|
||||
assertThatIllegalStateException().isThrownBy(() -> ofTestRequestFactory().build(settings))
|
||||
.withMessage("Unable to set HTTP cookie handling using reflection");
|
||||
}
|
||||
|
||||
@Test
|
||||
void cookieHandlingDisable() {
|
||||
HttpClientSettings settings = HttpClientSettings.defaults().withCookieHandling(HttpCookieHandling.DISABLE);
|
||||
assertThatIllegalStateException().isThrownBy(() -> ofTestRequestFactory().build(settings))
|
||||
.withMessage("Unable to set HTTP cookie handling using reflection");
|
||||
}
|
||||
|
||||
@Override
|
||||
void connectWithSslBundleAndOptionsMismatch(String httpMethod) throws Exception {
|
||||
assertThatIllegalStateException().isThrownBy(() -> super.connectWithSslBundleAndOptionsMismatch(httpMethod))
|
||||
|
||||
+8
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.boot.http.client;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
@@ -25,6 +26,7 @@ import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
@@ -81,6 +83,12 @@ class SimpleClientHttpRequestFactoryBuilderTests
|
||||
super.redirectDontFollow(httpMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwsWhenCookieHandlingEnabled() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> ClientHttpRequestFactoryBuilder.simple()
|
||||
.build(HttpClientSettings.defaults().withCookieHandling(HttpCookieHandling.ENABLE)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpStatus getExpectedRedirect(HttpMethod httpMethod) {
|
||||
return (httpMethod != HttpMethod.GET) ? HttpStatus.FOUND : HttpStatus.OK;
|
||||
|
||||
+2
-2
@@ -52,14 +52,14 @@ class HttpClientAutoConfigurationTests {
|
||||
.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(
|
||||
HttpRedirects.DONT_FOLLOW, Duration.ofSeconds(1), Duration.ofSeconds(2), null)));
|
||||
null, HttpRedirects.DONT_FOLLOW, Duration.ofSeconds(1), Duration.ofSeconds(2), null)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotReplaceUserProvidedHttpClientSettings() {
|
||||
this.contextRunner.withUserConfiguration(TestHttpClientConfiguration.class)
|
||||
.run((context) -> assertThat(context.getBean(HttpClientSettings.class))
|
||||
.isEqualTo(new HttpClientSettings(null, Duration.ofSeconds(1), Duration.ofSeconds(2), null)));
|
||||
.isEqualTo(new HttpClientSettings(null, null, Duration.ofSeconds(1), Duration.ofSeconds(2), null)));
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
||||
+11
-1
@@ -21,6 +21,7 @@ import java.time.Duration;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
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.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
@@ -79,6 +80,15 @@ class HttpClientSettingsPropertyMapperTests {
|
||||
assertThat(result.readTimeout()).isEqualTo(Duration.ofSeconds(30));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapMapsCookieHandling() {
|
||||
HttpClientSettingsPropertyMapper mapper = new HttpClientSettingsPropertyMapper(null, null);
|
||||
TestHttpClientSettingsProperties properties = new TestHttpClientSettingsProperties();
|
||||
properties.setCookieHandling(HttpCookieHandling.DISABLE);
|
||||
HttpClientSettings result = mapper.map(properties);
|
||||
assertThat(result.cookieHandling()).isEqualTo(HttpCookieHandling.DISABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapMapsSslBundle() {
|
||||
SslBundle sslBundle = mock(SslBundle.class);
|
||||
@@ -93,7 +103,7 @@ class HttpClientSettingsPropertyMapperTests {
|
||||
|
||||
@Test
|
||||
void mapUsesBaseSettingsForMissingProperties() {
|
||||
HttpClientSettings baseSettings = new HttpClientSettings(HttpRedirects.FOLLOW_WHEN_POSSIBLE,
|
||||
HttpClientSettings baseSettings = new HttpClientSettings(null, HttpRedirects.FOLLOW_WHEN_POSSIBLE,
|
||||
Duration.ofSeconds(15), Duration.ofSeconds(25), null);
|
||||
HttpClientSettingsPropertyMapper mapper = new HttpClientSettingsPropertyMapper(null, baseSettings);
|
||||
TestHttpClientSettingsProperties properties = new TestHttpClientSettingsProperties();
|
||||
|
||||
+15
@@ -35,6 +35,7 @@ import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
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.ssl.SslBundle;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
@@ -481,6 +482,20 @@ public class RestTemplateBuilder {
|
||||
this.customizers, this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cookie handling strategy on the underlying
|
||||
* {@link ClientHttpRequestFactory}.
|
||||
* @param cookieHandling the cookie handling strategy
|
||||
* @return a new builder instance.
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public RestTemplateBuilder cookieHandling(HttpCookieHandling cookieHandling) {
|
||||
return new RestTemplateBuilder(this.clientSettings.withCookieHandling(cookieHandling),
|
||||
this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors,
|
||||
this.requestFactoryBuilder, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication,
|
||||
this.defaultHeaders, this.customizers, this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the redirect strategy on the underlying {@link ClientHttpRequestFactory}.
|
||||
* @param redirects the redirect strategy
|
||||
|
||||
+22
-4
@@ -41,6 +41,7 @@ import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.HttpClientSettings;
|
||||
import org.springframework.boot.http.client.HttpComponentsClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.HttpComponentsHttpClientBuilder.TlsSocketStrategyFactory;
|
||||
import org.springframework.boot.http.client.HttpCookieHandling;
|
||||
import org.springframework.boot.http.client.HttpRedirects;
|
||||
import org.springframework.boot.restclient.RestTemplateBuilder;
|
||||
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate;
|
||||
@@ -69,8 +70,7 @@ import org.springframework.web.util.UriTemplateHandler;
|
||||
* status code}.
|
||||
* <p>
|
||||
* A {@code TestRestTemplate} can optionally carry Basic authentication headers. If Apache
|
||||
* Http Client 4.3.2 or better is available (recommended) it will be used as the client,
|
||||
* and by default configured to ignore cookies.
|
||||
* Http Client 4.3.2 or better is available (recommended) it will be used as the client.
|
||||
* <p>
|
||||
* Note: To prevent injection problems this class intentionally does not extend
|
||||
* {@link RestTemplate}. If you need access to the underlying {@link RestTemplate} use
|
||||
@@ -160,10 +160,12 @@ public class TestRestTemplate {
|
||||
return builder;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static HttpComponentsClientHttpRequestFactoryBuilder applyHttpClientOptions(
|
||||
HttpComponentsClientHttpRequestFactoryBuilder builder, HttpClientOption[] httpClientOptions) {
|
||||
builder = builder.withDefaultRequestConfigCustomizer(
|
||||
new CookieSpecCustomizer(HttpClientOption.ENABLE_COOKIES.isPresent(httpClientOptions)));
|
||||
if (HttpClientOption.ENABLE_COOKIES.isPresent(httpClientOptions)) {
|
||||
builder = builder.withDefaultRequestConfigCustomizer(new CookieSpecCustomizer(true));
|
||||
}
|
||||
if (HttpClientOption.SSL.isPresent(httpClientOptions)) {
|
||||
builder = builder.withTlsSocketStrategyFactory(new SelfSignedTlsSocketStrategyFactory());
|
||||
}
|
||||
@@ -974,6 +976,19 @@ public class TestRestTemplate {
|
||||
return withClientSettings((settings) -> settings.withRedirects(redirects));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code TestRestTemplate} with the same configuration as this one,
|
||||
* except that it will apply the given {@link HttpCookieHandling}. The request factory
|
||||
* used is a new instance of the underlying {@link RestTemplate}'s request factory
|
||||
* type (when possible).
|
||||
* @param cookieHandling the new cookie handling
|
||||
* @return the new template
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public TestRestTemplate withCookieHandling(HttpCookieHandling cookieHandling) {
|
||||
return withClientSettings((settings) -> settings.withCookieHandling(cookieHandling));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code TestRestTemplate} with the same configuration as this one,
|
||||
* except that it will apply the given {@link HttpClientSettings}. The request factory
|
||||
@@ -1036,7 +1051,10 @@ public class TestRestTemplate {
|
||||
|
||||
/**
|
||||
* Enable cookies.
|
||||
* @deprecated since 4.1.0 for removal in 4.3.0 in favor of
|
||||
* {@link TestRestTemplate#withCookieHandling(HttpCookieHandling)}
|
||||
*/
|
||||
@Deprecated(since = "4.1.0", forRemoval = true)
|
||||
ENABLE_COOKIES,
|
||||
|
||||
/**
|
||||
|
||||
+22
@@ -26,6 +26,7 @@ import java.util.Base64;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.cookie.StandardCookieSpec;
|
||||
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
|
||||
import org.apache.hc.client5.http.impl.classic.RedirectExec;
|
||||
import org.apache.hc.client5.http.protocol.RedirectStrategy;
|
||||
@@ -35,6 +36,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
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.restclient.RestTemplateBuilder;
|
||||
import org.springframework.boot.resttestclient.TestRestTemplate.HttpClientOption;
|
||||
@@ -140,11 +142,31 @@ class TestRestTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("removal")
|
||||
void options() {
|
||||
RequestConfig config = getRequestConfig(new TestRestTemplate(HttpClientOption.ENABLE_COOKIES));
|
||||
assertThat(config.getCookieSpec()).isEqualTo("strict");
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultCookieSpecMatchesRestTemplate() {
|
||||
RequestConfig config = getRequestConfig(new TestRestTemplate());
|
||||
assertThat(config.getCookieSpec()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void withCookieHandling() {
|
||||
TestRestTemplate template = new TestRestTemplate();
|
||||
assertThat(getRequestConfig(template).getCookieSpec()).isNull();
|
||||
assertThat(getRequestConfig(template.withCookieHandling(HttpCookieHandling.ENABLE)).getCookieSpec())
|
||||
.isEqualTo(StandardCookieSpec.STRICT);
|
||||
assertThat(
|
||||
getRequestConfig(template.withCookieHandling(HttpCookieHandling.ENABLE_WHEN_POSSIBLE)).getCookieSpec())
|
||||
.isEqualTo(StandardCookieSpec.STRICT);
|
||||
assertThat(getRequestConfig(template.withCookieHandling(HttpCookieHandling.DISABLE)).getCookieSpec())
|
||||
.isEqualTo(StandardCookieSpec.IGNORE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void jdkBuilderCanBeSpecifiedWithSpecificRedirects() {
|
||||
RestTemplateBuilder builder = new RestTemplateBuilder()
|
||||
|
||||
Reference in New Issue
Block a user