Unify HTTP client redirect behavior and provide configuration option
Update `ClientHttpRequestFactoryBuilder` implementations to ensure that all libraries have consistent redirect follow behavior. Following of redirects is enabled by default. The `ClientHttpRequestFactorySettings` may be used to change if redirects should be followed. The `spring.http.client.redirects` property may also be used to update the default behavior. Closes gh-42879
This commit is contained in:
parent
a92001130f
commit
36a22fcd59
@ -58,8 +58,8 @@ public class HttpClientAutoConfiguration {
|
|||||||
ClientHttpRequestFactorySettings clientHttpRequestFactorySettings(HttpClientProperties httpClientProperties,
|
ClientHttpRequestFactorySettings clientHttpRequestFactorySettings(HttpClientProperties httpClientProperties,
|
||||||
ObjectProvider<SslBundles> sslBundles) {
|
ObjectProvider<SslBundles> sslBundles) {
|
||||||
SslBundle sslBundle = getSslBundle(httpClientProperties.getSsl(), sslBundles);
|
SslBundle sslBundle = getSslBundle(httpClientProperties.getSsl(), sslBundles);
|
||||||
return new ClientHttpRequestFactorySettings(httpClientProperties.getConnectTimeout(),
|
return new ClientHttpRequestFactorySettings(httpClientProperties.getRedirects(),
|
||||||
httpClientProperties.getReadTimeout(), sslBundle);
|
httpClientProperties.getConnectTimeout(), httpClientProperties.getReadTimeout(), sslBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SslBundle getSslBundle(HttpClientProperties.Ssl properties, ObjectProvider<SslBundles> sslBundles) {
|
private SslBundle getSslBundle(HttpClientProperties.Ssl properties, ObjectProvider<SslBundles> sslBundles) {
|
||||||
|
@ -21,6 +21,7 @@ import java.util.function.Supplier;
|
|||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||||
|
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ConfigurationProperties @ConfigurationProperties} for a Spring's blocking HTTP
|
* {@link ConfigurationProperties @ConfigurationProperties} for a Spring's blocking HTTP
|
||||||
@ -37,6 +38,11 @@ public class HttpClientProperties {
|
|||||||
*/
|
*/
|
||||||
private Factory factory;
|
private Factory factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handling for HTTP redirects.
|
||||||
|
*/
|
||||||
|
private Redirects redirects = Redirects.FOLLOW_WHEN_POSSIBLE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default connect timeout for a client HTTP request.
|
* Default connect timeout for a client HTTP request.
|
||||||
*/
|
*/
|
||||||
@ -60,6 +66,14 @@ public class HttpClientProperties {
|
|||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Redirects getRedirects() {
|
||||||
|
return this.redirects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRedirects(Redirects redirects) {
|
||||||
|
this.redirects = redirects;
|
||||||
|
}
|
||||||
|
|
||||||
public Duration getConnectTimeout() {
|
public Duration getConnectTimeout() {
|
||||||
return this.connectTimeout;
|
return this.connectTimeout;
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
|
|||||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||||
|
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||||
import org.springframework.boot.http.client.SimpleClientHttpRequestFactoryBuilder;
|
import org.springframework.boot.http.client.SimpleClientHttpRequestFactoryBuilder;
|
||||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||||
@ -60,10 +61,11 @@ class HttpClientAutoConfigurationTests {
|
|||||||
@Test
|
@Test
|
||||||
void configuresClientHttpRequestFactorySettings() {
|
void configuresClientHttpRequestFactorySettings() {
|
||||||
this.contextRunner.withPropertyValues(sslPropertyValues().toArray(String[]::new))
|
this.contextRunner.withPropertyValues(sslPropertyValues().toArray(String[]::new))
|
||||||
.withPropertyValues("spring.http.client.connect-timeout=10s", "spring.http.client.read-timeout=20s",
|
.withPropertyValues("spring.http.client.redirects=dont-follow", "spring.http.client.connect-timeout=10s",
|
||||||
"spring.http.client.ssl.bundle=test")
|
"spring.http.client.read-timeout=20s", "spring.http.client.ssl.bundle=test")
|
||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
ClientHttpRequestFactorySettings settings = context.getBean(ClientHttpRequestFactorySettings.class);
|
ClientHttpRequestFactorySettings settings = context.getBean(ClientHttpRequestFactorySettings.class);
|
||||||
|
assertThat(settings.redirects()).isEqualTo(Redirects.DONT_FOLLOW);
|
||||||
assertThat(settings.connectTimeout()).isEqualTo(Duration.ofSeconds(10));
|
assertThat(settings.connectTimeout()).isEqualTo(Duration.ofSeconds(10));
|
||||||
assertThat(settings.readTimeout()).isEqualTo(Duration.ofSeconds(20));
|
assertThat(settings.readTimeout()).isEqualTo(Duration.ofSeconds(20));
|
||||||
assertThat(settings.sslBundle().getKey().getAlias()).isEqualTo("alias1");
|
assertThat(settings.sslBundle().getKey().getAlias()).isEqualTo("alias1");
|
||||||
|
@ -1018,7 +1018,7 @@ public class TestRestTemplate {
|
|||||||
@SuppressWarnings("removal")
|
@SuppressWarnings("removal")
|
||||||
public CustomHttpComponentsClientHttpRequestFactory(HttpClientOption[] httpClientOptions,
|
public CustomHttpComponentsClientHttpRequestFactory(HttpClientOption[] httpClientOptions,
|
||||||
org.springframework.boot.web.client.ClientHttpRequestFactorySettings settings) {
|
org.springframework.boot.web.client.ClientHttpRequestFactorySettings settings) {
|
||||||
this(httpClientOptions, new ClientHttpRequestFactorySettings(settings.connectTimeout(),
|
this(httpClientOptions, new ClientHttpRequestFactorySettings(null, settings.connectTimeout(),
|
||||||
settings.readTimeout(), settings.sslBundle()));
|
settings.readTimeout(), settings.sslBundle()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ import org.springframework.http.client.ClientHttpRequestFactory;
|
|||||||
/**
|
/**
|
||||||
* Settings that can be applied when creating a {@link ClientHttpRequestFactory}.
|
* Settings that can be applied when creating a {@link ClientHttpRequestFactory}.
|
||||||
*
|
*
|
||||||
|
* @param redirects the follow redirect strategy to use or null to redirect whenever the
|
||||||
|
* underlying library allows it
|
||||||
* @param connectTimeout the connect timeout
|
* @param connectTimeout the connect timeout
|
||||||
* @param readTimeout the read timeout
|
* @param readTimeout the read timeout
|
||||||
* @param sslBundle the SSL bundle providing SSL configuration
|
* @param sslBundle the SSL bundle providing SSL configuration
|
||||||
@ -33,10 +35,15 @@ import org.springframework.http.client.ClientHttpRequestFactory;
|
|||||||
* @since 3.4.0
|
* @since 3.4.0
|
||||||
* @see ClientHttpRequestFactoryBuilder
|
* @see ClientHttpRequestFactoryBuilder
|
||||||
*/
|
*/
|
||||||
public record ClientHttpRequestFactorySettings(Duration connectTimeout, Duration readTimeout, SslBundle sslBundle) {
|
public record ClientHttpRequestFactorySettings(Redirects redirects, Duration connectTimeout, Duration readTimeout,
|
||||||
|
SslBundle sslBundle) {
|
||||||
|
|
||||||
private static final ClientHttpRequestFactorySettings defaults = new ClientHttpRequestFactorySettings(null, null,
|
private static final ClientHttpRequestFactorySettings defaults = new ClientHttpRequestFactorySettings(null, null,
|
||||||
null);
|
null, null);
|
||||||
|
|
||||||
|
public ClientHttpRequestFactorySettings {
|
||||||
|
redirects = (redirects != null) ? redirects : Redirects.FOLLOW_WHEN_POSSIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a new {@link ClientHttpRequestFactorySettings} instance with an updated
|
* Return a new {@link ClientHttpRequestFactorySettings} instance with an updated
|
||||||
@ -45,7 +52,7 @@ public record ClientHttpRequestFactorySettings(Duration connectTimeout, Duration
|
|||||||
* @return a new {@link ClientHttpRequestFactorySettings} instance
|
* @return a new {@link ClientHttpRequestFactorySettings} instance
|
||||||
*/
|
*/
|
||||||
public ClientHttpRequestFactorySettings withConnectTimeout(Duration connectTimeout) {
|
public ClientHttpRequestFactorySettings withConnectTimeout(Duration connectTimeout) {
|
||||||
return new ClientHttpRequestFactorySettings(connectTimeout, this.readTimeout, this.sslBundle);
|
return new ClientHttpRequestFactorySettings(this.redirects, connectTimeout, this.readTimeout, this.sslBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,7 +63,7 @@ public record ClientHttpRequestFactorySettings(Duration connectTimeout, Duration
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public ClientHttpRequestFactorySettings withReadTimeout(Duration readTimeout) {
|
public ClientHttpRequestFactorySettings withReadTimeout(Duration readTimeout) {
|
||||||
return new ClientHttpRequestFactorySettings(this.connectTimeout, readTimeout, this.sslBundle);
|
return new ClientHttpRequestFactorySettings(this.redirects, this.connectTimeout, readTimeout, this.sslBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,7 +73,17 @@ public record ClientHttpRequestFactorySettings(Duration connectTimeout, Duration
|
|||||||
* @return a new {@link ClientHttpRequestFactorySettings} instance
|
* @return a new {@link ClientHttpRequestFactorySettings} instance
|
||||||
*/
|
*/
|
||||||
public ClientHttpRequestFactorySettings withSslBundle(SslBundle sslBundle) {
|
public ClientHttpRequestFactorySettings withSslBundle(SslBundle sslBundle) {
|
||||||
return new ClientHttpRequestFactorySettings(this.connectTimeout, this.readTimeout, sslBundle);
|
return new ClientHttpRequestFactorySettings(this.redirects, this.connectTimeout, this.readTimeout, sslBundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new {@link ClientHttpRequestFactorySettings} instance with an updated
|
||||||
|
* redirect setting.
|
||||||
|
* @param redirects the new redirects setting
|
||||||
|
* @return a new {@link ClientHttpRequestFactorySettings} instance
|
||||||
|
*/
|
||||||
|
public ClientHttpRequestFactorySettings withRedirects(Redirects redirects) {
|
||||||
|
return new ClientHttpRequestFactorySettings(redirects, this.connectTimeout, this.readTimeout, this.sslBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,4 +105,26 @@ public record ClientHttpRequestFactorySettings(Duration connectTimeout, Duration
|
|||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect strategies.
|
||||||
|
*/
|
||||||
|
public enum Redirects {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Follow redirects (if the underlying library has support).
|
||||||
|
*/
|
||||||
|
FOLLOW_WHEN_POSSIBLE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Follow redirects (fail if the underlying library has not support).
|
||||||
|
*/
|
||||||
|
FOLLOW,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't follow redirects (fail if the underlying library has not support).
|
||||||
|
*/
|
||||||
|
DONT_FOLLOW
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.springframework.boot.http.client;
|
package org.springframework.boot.http.client;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -24,14 +25,21 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.apache.hc.client5.http.classic.HttpClient;
|
import org.apache.hc.client5.http.classic.HttpClient;
|
||||||
|
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
|
||||||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
||||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
|
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
|
||||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
|
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
|
||||||
|
import org.apache.hc.client5.http.protocol.RedirectStrategy;
|
||||||
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
|
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
|
||||||
import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier;
|
import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier;
|
||||||
|
import org.apache.hc.core5.http.HttpException;
|
||||||
|
import org.apache.hc.core5.http.HttpRequest;
|
||||||
|
import org.apache.hc.core5.http.HttpResponse;
|
||||||
import org.apache.hc.core5.http.io.SocketConfig;
|
import org.apache.hc.core5.http.io.SocketConfig;
|
||||||
|
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.PropertyMapper;
|
import org.springframework.boot.context.properties.PropertyMapper;
|
||||||
|
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||||
import org.springframework.boot.ssl.SslBundle;
|
import org.springframework.boot.ssl.SslBundle;
|
||||||
import org.springframework.boot.ssl.SslOptions;
|
import org.springframework.boot.ssl.SslOptions;
|
||||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||||
@ -72,31 +80,35 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
|
|||||||
@Override
|
@Override
|
||||||
protected HttpComponentsClientHttpRequestFactory createClientHttpRequestFactory(
|
protected HttpComponentsClientHttpRequestFactory createClientHttpRequestFactory(
|
||||||
ClientHttpRequestFactorySettings settings) {
|
ClientHttpRequestFactorySettings settings) {
|
||||||
HttpClient httpClient = createHttpClient(settings.readTimeout(), settings.sslBundle());
|
HttpClient httpClient = createHttpClient(settings);
|
||||||
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
||||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||||
map.from(settings::connectTimeout).asInt(Duration::toMillis).to(factory::setConnectTimeout);
|
map.from(settings::connectTimeout).asInt(Duration::toMillis).to(factory::setConnectTimeout);
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpClient createHttpClient(Duration readTimeout, SslBundle sslBundle) {
|
private HttpClient createHttpClient(ClientHttpRequestFactorySettings settings) {
|
||||||
return HttpClientBuilder.create()
|
return HttpClientBuilder.create()
|
||||||
.useSystemProperties()
|
.useSystemProperties()
|
||||||
.setConnectionManager(createConnectionManager(readTimeout, sslBundle))
|
.setRedirectStrategy(asRedirectStrategy(settings.redirects()))
|
||||||
|
.setConnectionManager(createConnectionManager(settings))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private PoolingHttpClientConnectionManager createConnectionManager(Duration readTimeout, SslBundle sslBundle) {
|
private RedirectStrategy asRedirectStrategy(Redirects redirects) {
|
||||||
PoolingHttpClientConnectionManagerBuilder connectionManagerBuilder = PoolingHttpClientConnectionManagerBuilder
|
return switch (redirects) {
|
||||||
.create();
|
case FOLLOW_WHEN_POSSIBLE -> DefaultRedirectStrategy.INSTANCE;
|
||||||
if (readTimeout != null) {
|
case FOLLOW -> DefaultRedirectStrategy.INSTANCE;
|
||||||
connectionManagerBuilder.setDefaultSocketConfig(createSocketConfig(readTimeout));
|
case DONT_FOLLOW -> NoFollowRedirectStrategy.INSTANCE;
|
||||||
}
|
};
|
||||||
if (sslBundle != null) {
|
}
|
||||||
connectionManagerBuilder.setTlsSocketStrategy(createTlsSocketStrategy(sslBundle));
|
|
||||||
}
|
private PoolingHttpClientConnectionManager createConnectionManager(ClientHttpRequestFactorySettings settings) {
|
||||||
PoolingHttpClientConnectionManager connectionManager = connectionManagerBuilder.useSystemProperties().build();
|
PoolingHttpClientConnectionManagerBuilder builder = PoolingHttpClientConnectionManagerBuilder.create();
|
||||||
return connectionManager;
|
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||||
|
map.from(settings::readTimeout).as(this::createSocketConfig).to(builder::setDefaultSocketConfig);
|
||||||
|
map.from(settings::sslBundle).as(this::createTlsSocketStrategy).to(builder::setTlsSocketStrategy);
|
||||||
|
return builder.useSystemProperties().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private DefaultClientTlsStrategy createTlsSocketStrategy(SslBundle sslBundle) {
|
private DefaultClientTlsStrategy createTlsSocketStrategy(SslBundle sslBundle) {
|
||||||
@ -110,6 +122,30 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
|
|||||||
return SocketConfig.custom().setSoTimeout((int) readTimeout.toMillis(), TimeUnit.MILLISECONDS).build();
|
return SocketConfig.custom().setSoTimeout((int) readTimeout.toMillis(), TimeUnit.MILLISECONDS).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link RedirectStrategy} that never follows redirects.
|
||||||
|
*/
|
||||||
|
private static final class NoFollowRedirectStrategy implements RedirectStrategy {
|
||||||
|
|
||||||
|
private static final RedirectStrategy INSTANCE = new NoFollowRedirectStrategy();
|
||||||
|
|
||||||
|
private NoFollowRedirectStrategy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRedirected(HttpRequest request, HttpResponse response, HttpContext context)
|
||||||
|
throws HttpException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getLocationURI(HttpRequest request, HttpResponse response, HttpContext context)
|
||||||
|
throws HttpException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static class Classes {
|
static class Classes {
|
||||||
|
|
||||||
static final String HTTP_CLIENTS = "org.apache.hc.client5.http.impl.classic.HttpClients";
|
static final String HTTP_CLIENTS = "org.apache.hc.client5.http.impl.classic.HttpClients";
|
||||||
|
@ -17,12 +17,13 @@
|
|||||||
package org.springframework.boot.http.client;
|
package org.springframework.boot.http.client;
|
||||||
|
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.time.Duration;
|
import java.net.http.HttpClient.Redirect;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.PropertyMapper;
|
import org.springframework.boot.context.properties.PropertyMapper;
|
||||||
|
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||||
import org.springframework.boot.ssl.SslBundle;
|
import org.springframework.boot.ssl.SslBundle;
|
||||||
import org.springframework.http.client.JdkClientHttpRequestFactory;
|
import org.springframework.http.client.JdkClientHttpRequestFactory;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
@ -59,24 +60,30 @@ public class JdkClientHttpRequestFactoryBuilder
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected JdkClientHttpRequestFactory createClientHttpRequestFactory(ClientHttpRequestFactorySettings settings) {
|
protected JdkClientHttpRequestFactory createClientHttpRequestFactory(ClientHttpRequestFactorySettings settings) {
|
||||||
HttpClient httpClient = createHttpClient(settings.connectTimeout(), settings.sslBundle());
|
HttpClient httpClient = createHttpClient(settings);
|
||||||
JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(httpClient);
|
JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(httpClient);
|
||||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||||
map.from(settings::readTimeout).to(requestFactory::setReadTimeout);
|
map.from(settings::readTimeout).to(requestFactory::setReadTimeout);
|
||||||
return requestFactory;
|
return requestFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpClient createHttpClient(Duration connectTimeout, SslBundle sslBundle) {
|
private HttpClient createHttpClient(ClientHttpRequestFactorySettings settings) {
|
||||||
HttpClient.Builder httpClientBuilder = HttpClient.newBuilder();
|
HttpClient.Builder httpClientBuilder = HttpClient.newBuilder();
|
||||||
if (connectTimeout != null) {
|
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||||
httpClientBuilder.connectTimeout(connectTimeout);
|
map.from(settings::connectTimeout).to(httpClientBuilder::connectTimeout);
|
||||||
}
|
map.from(settings::sslBundle).as(SslBundle::createSslContext).to(httpClientBuilder::sslContext);
|
||||||
if (sslBundle != null) {
|
map.from(settings::redirects).as(this::asHttpClientRedirect).to(httpClientBuilder::followRedirects);
|
||||||
httpClientBuilder.sslContext(sslBundle.createSslContext());
|
|
||||||
}
|
|
||||||
return httpClientBuilder.build();
|
return httpClientBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Redirect asHttpClientRedirect(Redirects redirects) {
|
||||||
|
return switch (redirects) {
|
||||||
|
case FOLLOW_WHEN_POSSIBLE -> Redirect.NORMAL;
|
||||||
|
case FOLLOW -> Redirect.NORMAL;
|
||||||
|
case DONT_FOLLOW -> Redirect.NEVER;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static class Classes {
|
static class Classes {
|
||||||
|
|
||||||
static final String HTTP_CLIENT = "java.net.http.HttpClient";
|
static final String HTTP_CLIENT = "java.net.http.HttpClient";
|
||||||
|
@ -24,11 +24,14 @@ import java.util.function.Consumer;
|
|||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.HttpClientTransport;
|
||||||
import org.eclipse.jetty.client.transport.HttpClientTransportDynamic;
|
import org.eclipse.jetty.client.transport.HttpClientTransportDynamic;
|
||||||
|
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
|
||||||
import org.eclipse.jetty.io.ClientConnector;
|
import org.eclipse.jetty.io.ClientConnector;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.PropertyMapper;
|
import org.springframework.boot.context.properties.PropertyMapper;
|
||||||
|
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||||
import org.springframework.boot.ssl.SslBundle;
|
import org.springframework.boot.ssl.SslBundle;
|
||||||
import org.springframework.http.client.JettyClientHttpRequestFactory;
|
import org.springframework.http.client.JettyClientHttpRequestFactory;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
@ -65,24 +68,44 @@ public final class JettyClientHttpRequestFactoryBuilder
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected JettyClientHttpRequestFactory createClientHttpRequestFactory(ClientHttpRequestFactorySettings settings) {
|
protected JettyClientHttpRequestFactory createClientHttpRequestFactory(ClientHttpRequestFactorySettings settings) {
|
||||||
JettyClientHttpRequestFactory requestFactory = createRequestFactory(settings.sslBundle());
|
JettyClientHttpRequestFactory requestFactory = createRequestFactory(settings);
|
||||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||||
map.from(settings::connectTimeout).asInt(Duration::toMillis).to(requestFactory::setConnectTimeout);
|
map.from(settings::connectTimeout).asInt(Duration::toMillis).to(requestFactory::setConnectTimeout);
|
||||||
map.from(settings::readTimeout).asInt(Duration::toMillis).to(requestFactory::setReadTimeout);
|
map.from(settings::readTimeout).asInt(Duration::toMillis).to(requestFactory::setReadTimeout);
|
||||||
return requestFactory;
|
return requestFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JettyClientHttpRequestFactory createRequestFactory(SslBundle sslBundle) {
|
private JettyClientHttpRequestFactory createRequestFactory(ClientHttpRequestFactorySettings settings) {
|
||||||
if (sslBundle != null) {
|
HttpClientTransport transport = createTransport(settings);
|
||||||
SSLContext sslContext = sslBundle.createSslContext();
|
HttpClient httpClient = new HttpClient(transport);
|
||||||
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
|
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||||
sslContextFactory.setSslContext(sslContext);
|
map.from(settings::redirects).as(this::followRedirects).to(httpClient::setFollowRedirects);
|
||||||
ClientConnector connector = new ClientConnector();
|
return new JettyClientHttpRequestFactory(httpClient);
|
||||||
connector.setSslContextFactory(sslContextFactory);
|
}
|
||||||
HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(connector));
|
|
||||||
return new JettyClientHttpRequestFactory(httpClient);
|
private HttpClientTransport createTransport(ClientHttpRequestFactorySettings settings) {
|
||||||
|
if (settings.sslBundle() == null) {
|
||||||
|
return new HttpClientTransportOverHTTP();
|
||||||
}
|
}
|
||||||
return new JettyClientHttpRequestFactory();
|
ClientConnector connector = createClientConnector(settings.sslBundle());
|
||||||
|
return new HttpClientTransportDynamic(connector);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientConnector createClientConnector(SslBundle sslBundle) {
|
||||||
|
SSLContext sslContext = sslBundle.createSslContext();
|
||||||
|
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
|
||||||
|
sslContextFactory.setSslContext(sslContext);
|
||||||
|
ClientConnector connector = new ClientConnector();
|
||||||
|
connector.setSslContextFactory(sslContextFactory);
|
||||||
|
return connector;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean followRedirects(Redirects redirects) {
|
||||||
|
return switch (redirects) {
|
||||||
|
case FOLLOW_WHEN_POSSIBLE -> true;
|
||||||
|
case FOLLOW -> true;
|
||||||
|
case DONT_FOLLOW -> false;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Classes {
|
static class Classes {
|
||||||
|
@ -28,6 +28,7 @@ import reactor.netty.http.client.HttpClient;
|
|||||||
import reactor.netty.tcp.SslProvider.SslContextSpec;
|
import reactor.netty.tcp.SslProvider.SslContextSpec;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.PropertyMapper;
|
import org.springframework.boot.context.properties.PropertyMapper;
|
||||||
|
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||||
import org.springframework.boot.ssl.SslBundle;
|
import org.springframework.boot.ssl.SslBundle;
|
||||||
import org.springframework.boot.ssl.SslManagerBundle;
|
import org.springframework.boot.ssl.SslManagerBundle;
|
||||||
import org.springframework.boot.ssl.SslOptions;
|
import org.springframework.boot.ssl.SslOptions;
|
||||||
@ -68,22 +69,30 @@ public final class ReactorClientHttpRequestFactoryBuilder
|
|||||||
@Override
|
@Override
|
||||||
protected ReactorClientHttpRequestFactory createClientHttpRequestFactory(
|
protected ReactorClientHttpRequestFactory createClientHttpRequestFactory(
|
||||||
ClientHttpRequestFactorySettings settings) {
|
ClientHttpRequestFactorySettings settings) {
|
||||||
ReactorClientHttpRequestFactory requestFactory = createRequestFactory(settings.sslBundle());
|
ReactorClientHttpRequestFactory requestFactory = createRequestFactory(settings);
|
||||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||||
map.from(settings::connectTimeout).asInt(Duration::toMillis).to(requestFactory::setConnectTimeout);
|
map.from(settings::connectTimeout).asInt(Duration::toMillis).to(requestFactory::setConnectTimeout);
|
||||||
map.from(settings::readTimeout).asInt(Duration::toMillis).to(requestFactory::setReadTimeout);
|
map.from(settings::readTimeout).asInt(Duration::toMillis).to(requestFactory::setReadTimeout);
|
||||||
return requestFactory;
|
return requestFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReactorClientHttpRequestFactory createRequestFactory(SslBundle sslBundle) {
|
private ReactorClientHttpRequestFactory createRequestFactory(ClientHttpRequestFactorySettings settings) {
|
||||||
HttpClient httpClient = HttpClient.create();
|
HttpClient httpClient = applyDefaults(HttpClient.create());
|
||||||
httpClient = applyDefaults(httpClient);
|
httpClient = httpClient.followRedirect(followRedirects(settings.redirects()));
|
||||||
if (sslBundle != null) {
|
if (settings.sslBundle() != null) {
|
||||||
httpClient = httpClient.secure((ThrowingConsumer.of((spec) -> configureSsl(spec, sslBundle))));
|
httpClient = httpClient.secure((ThrowingConsumer.of((spec) -> configureSsl(spec, settings.sslBundle()))));
|
||||||
}
|
}
|
||||||
return new ReactorClientHttpRequestFactory(httpClient);
|
return new ReactorClientHttpRequestFactory(httpClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean followRedirects(Redirects redirects) {
|
||||||
|
return switch (redirects) {
|
||||||
|
case FOLLOW_WHEN_POSSIBLE -> true;
|
||||||
|
case FOLLOW -> true;
|
||||||
|
case DONT_FOLLOW -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
HttpClient applyDefaults(HttpClient httpClient) {
|
HttpClient applyDefaults(HttpClient httpClient) {
|
||||||
// Aligns with ReactorClientHttpRequestFactory defaults
|
// Aligns with ReactorClientHttpRequestFactory defaults
|
||||||
return httpClient.compress(true);
|
return httpClient.compress(true);
|
||||||
|
@ -23,6 +23,7 @@ import java.time.Duration;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.PropertyMapper;
|
import org.springframework.boot.context.properties.PropertyMapper;
|
||||||
|
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||||
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
|
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
|
||||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@ -73,7 +74,9 @@ final class ReflectiveComponentsClientHttpRequestFactoryBuilder<T extends Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void configure(ClientHttpRequestFactory requestFactory, ClientHttpRequestFactorySettings settings) {
|
private void configure(ClientHttpRequestFactory requestFactory, ClientHttpRequestFactorySettings settings) {
|
||||||
Assert.state(settings.sslBundle() == null, "Unable to set SSL bundler using reflection");
|
Assert.state(settings.sslBundle() == null, "Unable to set SSL bundle using reflection");
|
||||||
|
Assert.state(settings.redirects() == Redirects.FOLLOW_WHEN_POSSIBLE,
|
||||||
|
"Unable to set redirect follow using reflection");
|
||||||
ClientHttpRequestFactory unwrapped = unwrapRequestFactoryIfNecessary(requestFactory);
|
ClientHttpRequestFactory unwrapped = unwrapRequestFactoryIfNecessary(requestFactory);
|
||||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||||
map.from(settings::connectTimeout).to((connectTimeout) -> setConnectTimeout(unwrapped, connectTimeout));
|
map.from(settings::connectTimeout).to((connectTimeout) -> setConnectTimeout(unwrapped, connectTimeout));
|
||||||
|
@ -27,6 +27,7 @@ import javax.net.ssl.HttpsURLConnection;
|
|||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.PropertyMapper;
|
import org.springframework.boot.context.properties.PropertyMapper;
|
||||||
|
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||||
import org.springframework.boot.ssl.SslBundle;
|
import org.springframework.boot.ssl.SslBundle;
|
||||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@ -64,8 +65,7 @@ public final class SimpleClientHttpRequestFactoryBuilder
|
|||||||
@Override
|
@Override
|
||||||
protected SimpleClientHttpRequestFactory createClientHttpRequestFactory(ClientHttpRequestFactorySettings settings) {
|
protected SimpleClientHttpRequestFactory createClientHttpRequestFactory(ClientHttpRequestFactorySettings settings) {
|
||||||
SslBundle sslBundle = settings.sslBundle();
|
SslBundle sslBundle = settings.sslBundle();
|
||||||
SimpleClientHttpRequestFactory requestFactory = (sslBundle != null)
|
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpsRequestFactory(settings);
|
||||||
? new SimpleClientHttpsRequestFactory(sslBundle) : new SimpleClientHttpRequestFactory();
|
|
||||||
Assert.state(sslBundle == null || !sslBundle.getOptions().isSpecified(),
|
Assert.state(sslBundle == null || !sslBundle.getOptions().isSpecified(),
|
||||||
"SSL Options cannot be specified with Java connections");
|
"SSL Options cannot be specified with Java connections");
|
||||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||||
@ -75,23 +75,27 @@ public final class SimpleClientHttpRequestFactoryBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link SimpleClientHttpsRequestFactory} to configure SSL from an {@link SslBundle}.
|
* {@link SimpleClientHttpsRequestFactory} to configure SSL from an {@link SslBundle}
|
||||||
|
* and {@link Redirects}.
|
||||||
*/
|
*/
|
||||||
private static class SimpleClientHttpsRequestFactory extends SimpleClientHttpRequestFactory {
|
private static class SimpleClientHttpsRequestFactory extends SimpleClientHttpRequestFactory {
|
||||||
|
|
||||||
private final SslBundle sslBundle;
|
private final ClientHttpRequestFactorySettings settings;
|
||||||
|
|
||||||
SimpleClientHttpsRequestFactory(SslBundle sslBundle) {
|
SimpleClientHttpsRequestFactory(ClientHttpRequestFactorySettings settings) {
|
||||||
this.sslBundle = sslBundle;
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
|
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
|
||||||
super.prepareConnection(connection, httpMethod);
|
super.prepareConnection(connection, httpMethod);
|
||||||
if (this.sslBundle != null && connection instanceof HttpsURLConnection secureConnection) {
|
if (this.settings.sslBundle() != null && connection instanceof HttpsURLConnection secureConnection) {
|
||||||
SSLSocketFactory socketFactory = this.sslBundle.createSslContext().getSocketFactory();
|
SSLSocketFactory socketFactory = this.settings.sslBundle().createSslContext().getSocketFactory();
|
||||||
secureConnection.setSSLSocketFactory(socketFactory);
|
secureConnection.setSSLSocketFactory(socketFactory);
|
||||||
}
|
}
|
||||||
|
if (this.settings.redirects() == Redirects.DONT_FOLLOW) {
|
||||||
|
connection.setInstanceFollowRedirects(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ public record ClientHttpRequestFactorySettings(Duration connectTimeout, Duration
|
|||||||
}
|
}
|
||||||
|
|
||||||
org.springframework.boot.http.client.ClientHttpRequestFactorySettings adapt() {
|
org.springframework.boot.http.client.ClientHttpRequestFactorySettings adapt() {
|
||||||
return new org.springframework.boot.http.client.ClientHttpRequestFactorySettings(connectTimeout(),
|
return new org.springframework.boot.http.client.ClientHttpRequestFactorySettings(null, connectTimeout(),
|
||||||
readTimeout(), sslBundle());
|
readTimeout(), sslBundle());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ package org.springframework.boot.http.client;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.ValueSource;
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
|
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||||
import org.springframework.boot.ssl.SslBundle;
|
import org.springframework.boot.ssl.SslBundle;
|
||||||
import org.springframework.boot.ssl.SslBundleKey;
|
import org.springframework.boot.ssl.SslBundleKey;
|
||||||
import org.springframework.boot.ssl.jks.JksSslStoreBundle;
|
import org.springframework.boot.ssl.jks.JksSslStoreBundle;
|
||||||
@ -41,8 +43,10 @@ import org.springframework.boot.web.server.Ssl;
|
|||||||
import org.springframework.boot.web.server.Ssl.ClientAuth;
|
import org.springframework.boot.web.server.Ssl.ClientAuth;
|
||||||
import org.springframework.boot.web.server.WebServer;
|
import org.springframework.boot.web.server.WebServer;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.client.ClientHttpRequest;
|
import org.springframework.http.client.ClientHttpRequest;
|
||||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||||
|
import org.springframework.http.client.ClientHttpResponse;
|
||||||
import org.springframework.util.StreamUtils;
|
import org.springframework.util.StreamUtils;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@ -116,6 +120,45 @@ abstract class AbstractClientHttpRequestFactoryBuilderTests<T extends ClientHttp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void redirectDefault() throws Exception {
|
||||||
|
testRedirect(null, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void redirectFollow() throws Exception {
|
||||||
|
testRedirect(ClientHttpRequestFactorySettings.defaults().withRedirects(Redirects.FOLLOW), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void redirectDontFollow() throws Exception {
|
||||||
|
testRedirect(ClientHttpRequestFactorySettings.defaults().withRedirects(Redirects.DONT_FOLLOW),
|
||||||
|
HttpStatus.FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testRedirect(ClientHttpRequestFactorySettings settings, HttpStatus expectedStatus)
|
||||||
|
throws URISyntaxException, IOException {
|
||||||
|
TomcatServletWebServerFactory webServerFactory = new TomcatServletWebServerFactory(0);
|
||||||
|
WebServer webServer = webServerFactory
|
||||||
|
.getWebServer((context) -> context.addServlet("test", TestServlet.class).addMapping("/"));
|
||||||
|
try {
|
||||||
|
webServer.start();
|
||||||
|
int port = webServer.getPort();
|
||||||
|
URI uri = new URI("http://localhost:%s".formatted(port) + "/redirect");
|
||||||
|
ClientHttpRequestFactory requestFactory = this.builder.build(settings);
|
||||||
|
ClientHttpRequest request = requestFactory.createRequest(uri, HttpMethod.GET);
|
||||||
|
ClientHttpResponse response = request.execute();
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(expectedStatus);
|
||||||
|
if (expectedStatus == HttpStatus.OK) {
|
||||||
|
assertThat(response.getBody()).asString(StandardCharsets.UTF_8)
|
||||||
|
.contains("Received GET request to /redirected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
webServer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ClientHttpRequest request(ClientHttpRequestFactory factory, URI uri, String method) throws IOException {
|
private ClientHttpRequest request(ClientHttpRequestFactory factory, URI uri, String method) throws IOException {
|
||||||
return factory.createRequest(uri, HttpMethod.valueOf(method));
|
return factory.createRequest(uri, HttpMethod.valueOf(method));
|
||||||
}
|
}
|
||||||
@ -143,6 +186,10 @@ abstract class AbstractClientHttpRequestFactoryBuilderTests<T extends ClientHttp
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
|
public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
|
||||||
|
if ("/redirect".equals(req.getRequestURI())) {
|
||||||
|
res.sendRedirect("/redirected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
res.getWriter().println("Received " + req.getMethod() + " request to " + req.getRequestURI());
|
res.getWriter().println("Received " + req.getMethod() + " request to " + req.getRequestURI());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import java.time.Duration;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||||
import org.springframework.boot.ssl.SslBundle;
|
import org.springframework.boot.ssl.SslBundle;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@ -35,8 +36,18 @@ class ClientHttpRequestFactorySettingsTests {
|
|||||||
private static final Duration ONE_SECOND = Duration.ofSeconds(1);
|
private static final Duration ONE_SECOND = Duration.ofSeconds(1);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void defaultsHasNullValues() {
|
void defaults() {
|
||||||
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults();
|
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults();
|
||||||
|
assertThat(settings.redirects()).isEqualTo(Redirects.FOLLOW_WHEN_POSSIBLE);
|
||||||
|
assertThat(settings.connectTimeout()).isNull();
|
||||||
|
assertThat(settings.readTimeout()).isNull();
|
||||||
|
assertThat(settings.sslBundle()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createWithNullsUsesDefaults() {
|
||||||
|
ClientHttpRequestFactorySettings settings = new ClientHttpRequestFactorySettings(null, null, null, null);
|
||||||
|
assertThat(settings.redirects()).isEqualTo(Redirects.FOLLOW_WHEN_POSSIBLE);
|
||||||
assertThat(settings.connectTimeout()).isNull();
|
assertThat(settings.connectTimeout()).isNull();
|
||||||
assertThat(settings.readTimeout()).isNull();
|
assertThat(settings.readTimeout()).isNull();
|
||||||
assertThat(settings.sslBundle()).isNull();
|
assertThat(settings.sslBundle()).isNull();
|
||||||
@ -70,4 +81,11 @@ class ClientHttpRequestFactorySettingsTests {
|
|||||||
assertThat(settings.sslBundle()).isSameAs(sslBundle);
|
assertThat(settings.sslBundle()).isSameAs(sslBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void withRedirectsReturnsInstanceWithUpdatedRedirect() {
|
||||||
|
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
|
||||||
|
.withRedirects(Redirects.DONT_FOLLOW);
|
||||||
|
assertThat(settings.redirects()).isEqualTo(Redirects.DONT_FOLLOW);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import java.time.Duration;
|
|||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.client.BufferingClientHttpRequestFactory;
|
import org.springframework.http.client.BufferingClientHttpRequestFactory;
|
||||||
import org.springframework.http.client.ClientHttpRequest;
|
import org.springframework.http.client.ClientHttpRequest;
|
||||||
@ -52,6 +53,22 @@ class ReflectiveComponentsClientHttpRequestFactoryBuilderTests
|
|||||||
.withMessage("Unable to set SSL bundler using reflection");
|
.withMessage("Unable to set SSL bundler using reflection");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void redirectFollow() throws Exception {
|
||||||
|
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
|
||||||
|
.withRedirects(Redirects.FOLLOW);
|
||||||
|
assertThatIllegalStateException().isThrownBy(() -> ofTestRequestFactory().build(settings))
|
||||||
|
.withMessage("Unable to set redirect follow using reflection");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void redirectDontFollow() throws Exception {
|
||||||
|
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
|
||||||
|
.withRedirects(Redirects.DONT_FOLLOW);
|
||||||
|
assertThatIllegalStateException().isThrownBy(() -> ofTestRequestFactory().build(settings))
|
||||||
|
.withMessage("Unable to set redirect follow using reflection");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void buildWithClassCreatesFactory() {
|
void buildWithClassCreatesFactory() {
|
||||||
assertThat(ofTestRequestFactory().build()).isInstanceOf(TestClientHttpRequestFactory.class);
|
assertThat(ofTestRequestFactory().build()).isInstanceOf(TestClientHttpRequestFactory.class);
|
||||||
|
@ -36,8 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
*
|
*
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
*/
|
*/
|
||||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
|
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "management.server.port:0" })
|
||||||
properties = { "management.server.port:0", "spring.http.client.factory=simple" })
|
|
||||||
class SampleActuatorUiApplicationPortTests {
|
class SampleActuatorUiApplicationPortTests {
|
||||||
|
|
||||||
@LocalServerPort
|
@LocalServerPort
|
||||||
|
@ -39,8 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
*
|
*
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
*/
|
*/
|
||||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
|
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "server.error.include-message=always" })
|
||||||
properties = { "server.error.include-message=always", "spring.http.client.factory=simple" })
|
|
||||||
class SampleActuatorUiApplicationTests {
|
class SampleActuatorUiApplicationTests {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -51,7 +50,7 @@ class SampleActuatorUiApplicationTests {
|
|||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
|
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
|
||||||
ResponseEntity<String> entity = this.restTemplate.withBasicAuth("user", getPassword())
|
ResponseEntity<String> entity = this.restTemplate.withBasicAuth("user", getPassword())
|
||||||
.exchange("/", HttpMethod.GET, new HttpEntity<>(headers), String.class);
|
.exchange("/", HttpMethod.GET, new HttpEntity<Void>(headers), String.class);
|
||||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
assertThat(entity.getBody()).contains("<title>Hello");
|
assertThat(entity.getBody()).contains("<title>Hello");
|
||||||
}
|
}
|
||||||
@ -75,7 +74,7 @@ class SampleActuatorUiApplicationTests {
|
|||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
|
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
|
||||||
ResponseEntity<String> entity = this.restTemplate.withBasicAuth("user", getPassword())
|
ResponseEntity<String> entity = this.restTemplate.withBasicAuth("user", getPassword())
|
||||||
.exchange("/error", HttpMethod.GET, new HttpEntity<>(headers), String.class);
|
.exchange("/error", HttpMethod.GET, new HttpEntity<Void>(headers), String.class);
|
||||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
|
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
assertThat(entity.getBody()).contains("<html>")
|
assertThat(entity.getBody()).contains("<html>")
|
||||||
.contains("<body>")
|
.contains("<body>")
|
||||||
|
@ -25,8 +25,12 @@ import java.util.Map;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||||
|
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||||
|
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||||
|
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
@ -37,6 +41,7 @@ import org.springframework.http.RequestEntity;
|
|||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@ -48,12 +53,22 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
*/
|
*/
|
||||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||||
properties = { "server.servlet.session.timeout:2", "spring.http.client.factory=simple" })
|
properties = { "server.servlet.session.timeout:2" })
|
||||||
class SampleSessionJdbcApplicationTests {
|
class SampleSessionJdbcApplicationTests {
|
||||||
|
|
||||||
|
private static final ClientHttpRequestFactorySettings DONT_FOLLOW_REDIRECTS = ClientHttpRequestFactorySettings
|
||||||
|
.defaults()
|
||||||
|
.withRedirects(Redirects.DONT_FOLLOW);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RestTemplateBuilder restTemplateBuilder;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private TestRestTemplate restTemplate;
|
private TestRestTemplate restTemplate;
|
||||||
|
|
||||||
|
@LocalServerPort
|
||||||
|
private String port;
|
||||||
|
|
||||||
private static final URI ROOT_URI = URI.create("/");
|
private static final URI ROOT_URI = URI.create("/");
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -68,14 +83,15 @@ class SampleSessionJdbcApplicationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String performLogin() {
|
private String performLogin() {
|
||||||
|
RestTemplate restTemplate = this.restTemplateBuilder.requestFactorySettings(DONT_FOLLOW_REDIRECTS).build();
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
|
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
|
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
|
||||||
form.set("username", "user");
|
form.set("username", "user");
|
||||||
form.set("password", "password");
|
form.set("password", "password");
|
||||||
ResponseEntity<String> entity = this.restTemplate.exchange("/login", HttpMethod.POST,
|
ResponseEntity<String> entity = restTemplate.exchange("http://localhost:" + this.port + "/login",
|
||||||
new HttpEntity<>(form, headers), String.class);
|
HttpMethod.POST, new HttpEntity<>(form, headers), String.class);
|
||||||
return entity.getHeaders().getFirst("Set-Cookie");
|
return entity.getHeaders().getFirst("Set-Cookie");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2022 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -37,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
*
|
*
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
*/
|
*/
|
||||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.http.client.redirects=dont-follow")
|
||||||
class SampleGroovyTemplateApplicationTests {
|
class SampleGroovyTemplateApplicationTests {
|
||||||
|
|
||||||
@LocalServerPort
|
@LocalServerPort
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2022 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -37,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
*
|
*
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
*/
|
*/
|
||||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.http.client.redirects=dont-follow")
|
||||||
class SampleWebUiApplicationTests {
|
class SampleWebUiApplicationTests {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
Loading…
x
Reference in New Issue
Block a user