diff --git a/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java index da28a171756d..1dab112c85cc 100644 --- a/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java @@ -51,6 +51,31 @@ public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory private int readTimeout = -1; + /** + * Private constructor to be used by the Builder. + */ + private SimpleClientHttpRequestFactory(Builder builder) { + this.proxy = builder.proxy; + this.chunkSize = builder.chunkSize; + this.connectTimeout = builder.connectTimeout; + this.readTimeout = builder.readTimeout; + } + + /** + * Default constructor. + * Use {@link #builder()} for a fluent API for configuration. + */ + public SimpleClientHttpRequestFactory() { + } + + /** + * Creates a new {@code Builder} for {@link SimpleClientHttpRequestFactory}. + * @return a new {@code Builder} instance + * @since 7.0 + */ + public static Builder builder() { + return new Builder(); + } /** * Set the {@link Proxy} to use for this request factory. @@ -163,4 +188,99 @@ protected void prepareConnection(HttpURLConnection connection, String httpMethod connection.setRequestMethod(httpMethod); } + + /** + * A builder for {@link SimpleClientHttpRequestFactory}. + * @since 7.0 + */ + public static class Builder { + + @Nullable + private Proxy proxy; + + private int chunkSize = DEFAULT_CHUNK_SIZE; + + private int connectTimeout = -1; + + private int readTimeout = -1; + + /** + * Set the {@link Proxy} to use for this request factory. + * @param proxy the proxy to use + * @return this builder + */ + public Builder proxy(Proxy proxy) { + this.proxy = proxy; + return this; + } + + /** + * Set the number of bytes to write in each chunk when not buffering request + * bodies locally. + * @param chunkSize the chunk size + * @return this builder + */ + public Builder chunkSize(int chunkSize) { + this.chunkSize = chunkSize; + return this; + } + + /** + * Set the underlying URLConnection's connect timeout (in milliseconds). + * A timeout value of 0 specifies an infinite timeout. + *
Default is the system's default timeout. + * @param connectTimeout the connect timeout in milliseconds + * @return this builder + */ + public Builder connectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + return this; + } + + /** + * Set the underlying URLConnection's connect timeout as {@code Duration}. + * A timeout value of 0 specifies an infinite timeout. + *
Default is the system's default timeout. + * @param connectTimeout the connect timeout duration + * @return this builder + */ + public Builder connectTimeout(Duration connectTimeout) { + Assert.notNull(connectTimeout, "ConnectTimeout must not be null"); + this.connectTimeout = (int) connectTimeout.toMillis(); + return this; + } + + /** + * Set the underlying URLConnection's read timeout (in milliseconds). + * A timeout value of 0 specifies an infinite timeout. + *
Default is the system's default timeout. + * @param readTimeout the read timeout in milliseconds + * @return this builder + */ + public Builder readTimeout(int readTimeout) { + this.readTimeout = readTimeout; + return this; + } + + /** + * Set the underlying URLConnection's read timeout as {@code Duration}. + * A timeout value of 0 specifies an infinite timeout. + *
Default is the system's default timeout. + * @param readTimeout the read timeout duration + * @return this builder + */ + public Builder readTimeout(Duration readTimeout) { + Assert.notNull(readTimeout, "ReadTimeout must not be null"); + this.readTimeout = (int) readTimeout.toMillis(); + return this; + } + + /** + * Builds a new {@link SimpleClientHttpRequestFactory} instance with the configured properties. + * @return a new {@link SimpleClientHttpRequestFactory} + */ + public SimpleClientHttpRequestFactory build() { + return new SimpleClientHttpRequestFactory(this); + } + } } diff --git a/spring-web/src/test/java/org/springframework/http/client/SimpleClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/SimpleClientHttpRequestFactoryTests.java index 161011be5ba3..2bf10d196764 100644 --- a/spring-web/src/test/java/org/springframework/http/client/SimpleClientHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/SimpleClientHttpRequestFactoryTests.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.ProtocolException; +import java.net.Proxy; import java.net.URI; import java.net.URL; import java.util.Collections; @@ -38,7 +39,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -public class SimpleClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTests { +class SimpleClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTests { @Override protected ClientHttpRequestFactory createRequestFactory() { @@ -117,6 +118,23 @@ public void headerWithNullValue() { verify(urlConnection, times(1)).addRequestProperty("foo", ""); } + @Test + void builderShouldApplyConfiguredTimeouts() throws IOException { + Proxy proxy = Proxy.NO_PROXY; + var factory = SimpleClientHttpRequestFactory.builder() + .connectTimeout(1234) + .readTimeout(5678) + .proxy(proxy) + .build(); + + URL url = new URL("https://example.com"); + + HttpURLConnection httpURLConnection = factory.openConnection(url, proxy); + factory.prepareConnection(httpURLConnection, "GET"); + + assertThat(httpURLConnection.getConnectTimeout()).isEqualTo(1234); + assertThat(httpURLConnection.getReadTimeout()).isEqualTo(5678); + } private static class TestHttpURLConnection extends HttpURLConnection {