From 191ac10009cdf1f0663221dad16993493ca50205 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 1 Aug 2023 16:16:13 +0200 Subject: [PATCH] Deprecate support for OkHttp Closes gh-36632 --- .../src/docs/asciidoc/io/rest-client.adoc | 2 +- .../web/client/TestRestTemplateTests.java | 9 ++--- .../client/ClientHttpRequestFactories.java | 21 ++++++----- ...lientHttpRequestFactoriesRuntimeHints.java | 15 +++++--- ...lientHttpRequestFactoriesOkHttp3Tests.java | 4 ++- ...lientHttpRequestFactoriesOkHttp4Tests.java | 4 ++- ...HttpRequestFactoriesRuntimeHintsTests.java | 2 ++ .../ClientHttpRequestFactoriesTests.java | 2 ++ ...eSenderBuilderOkHttp3IntegrationTests.java | 22 ++++++------ .../build.gradle | 2 +- .../couchbase/SecureCouchbaseContainer.java | 36 +++++++++++-------- 11 files changed, 73 insertions(+), 46 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc index b371623f77..84eff6c87e 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc @@ -181,8 +181,8 @@ Spring Boot will auto-detect which HTTP client to use with `RestClient` and `Res In order of preference, the following clients are supported: . Apache HttpClient -. OkHttp . Jetty HttpClient +. OkHttp (deprecated) . Simple JDK client (`HttpURLConnection`) If multiple clients are available on the classpath, the most preferred client will be used. diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java index 53cb7b8366..11b9b881af 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java @@ -38,7 +38,7 @@ import org.springframework.http.RequestEntity; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; +import org.springframework.http.client.JettyClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.http.client.MockClientHttpRequest; @@ -86,15 +86,16 @@ class TestRestTemplateTests { @Test void doNotReplaceCustomRequestFactory() { - RestTemplateBuilder builder = new RestTemplateBuilder().requestFactory(OkHttp3ClientHttpRequestFactory.class); + RestTemplateBuilder builder = new RestTemplateBuilder() + .requestFactory(HttpComponentsClientHttpRequestFactory.class); TestRestTemplate testRestTemplate = new TestRestTemplate(builder); assertThat(testRestTemplate.getRestTemplate().getRequestFactory()) - .isInstanceOf(OkHttp3ClientHttpRequestFactory.class); + .isInstanceOf(HttpComponentsClientHttpRequestFactory.class); } @Test void useTheSameRequestFactoryClassWithBasicAuth() { - OkHttp3ClientHttpRequestFactory customFactory = new OkHttp3ClientHttpRequestFactory(); + JettyClientHttpRequestFactory customFactory = new JettyClientHttpRequestFactory(); RestTemplateBuilder builder = new RestTemplateBuilder().requestFactory(() -> customFactory); TestRestTemplate testRestTemplate = new TestRestTemplate(builder).withBasicAuth("test", "test"); RestTemplate restTemplate = testRestTemplate.getRestTemplate(); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java index 93e4e407c8..0ecdc850a0 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java @@ -89,24 +89,25 @@ public final class ClientHttpRequestFactories { * dependencies {@link ClassUtils#isPresent are available} is returned: *
    *
  1. {@link HttpComponentsClientHttpRequestFactory}
  2. - *
  3. {@link OkHttp3ClientHttpRequestFactory}
  4. *
  5. {@link JettyClientHttpRequestFactory}
  6. + *
  7. {@link OkHttp3ClientHttpRequestFactory} (deprecated)
  8. *
  9. {@link SimpleClientHttpRequestFactory}
  10. *
* @param settings the settings to apply * @return a new {@link ClientHttpRequestFactory} */ + @SuppressWarnings("removal") public static ClientHttpRequestFactory get(ClientHttpRequestFactorySettings settings) { Assert.notNull(settings, "Settings must not be null"); if (APACHE_HTTP_CLIENT_PRESENT) { return HttpComponents.get(settings); } - if (OKHTTP_CLIENT_PRESENT) { - return OkHttp.get(settings); - } if (JETTY_CLIENT_PRESENT) { return Jetty.get(settings); } + if (OKHTTP_CLIENT_PRESENT) { + return OkHttp.get(settings); + } return Simple.get(settings); } @@ -119,7 +120,7 @@ public final class ClientHttpRequestFactories { *
  • {@link HttpComponentsClientHttpRequestFactory}
  • *
  • {@link JdkClientHttpRequestFactory}
  • *
  • {@link JettyClientHttpRequestFactory}
  • - *
  • {@link OkHttp3ClientHttpRequestFactory}
  • + *
  • {@link OkHttp3ClientHttpRequestFactory} (deprecated)
  • *
  • {@link SimpleClientHttpRequestFactory}
  • * * A {@code requestFactoryType} of {@link ClientHttpRequestFactory} is equivalent to @@ -129,7 +130,7 @@ public final class ClientHttpRequestFactories { * @param settings the settings to apply * @return a new {@link ClientHttpRequestFactory} instance */ - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "removal" }) public static T get(Class requestFactoryType, ClientHttpRequestFactorySettings settings) { Assert.notNull(settings, "Settings must not be null"); @@ -139,9 +140,6 @@ public final class ClientHttpRequestFactories { if (requestFactoryType == HttpComponentsClientHttpRequestFactory.class) { return (T) HttpComponents.get(settings); } - if (requestFactoryType == OkHttp3ClientHttpRequestFactory.class) { - return (T) OkHttp.get(settings); - } if (requestFactoryType == JettyClientHttpRequestFactory.class) { return (T) Jetty.get(settings); } @@ -151,6 +149,9 @@ public final class ClientHttpRequestFactories { if (requestFactoryType == SimpleClientHttpRequestFactory.class) { return (T) Simple.get(settings); } + if (requestFactoryType == OkHttp3ClientHttpRequestFactory.class) { + return (T) OkHttp.get(settings); + } return get(() -> createRequestFactory(requestFactoryType), settings); } @@ -220,6 +221,8 @@ public final class ClientHttpRequestFactories { /** * Support for {@link OkHttp3ClientHttpRequestFactory}. */ + @Deprecated(since = "3.2.0", forRemoval = true) + @SuppressWarnings("removal") static class OkHttp { static OkHttp3ClientHttpRequestFactory get(ClientHttpRequestFactorySettings settings) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHints.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHints.java index 90f3db9af1..45a3744a4b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHints.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHints.java @@ -56,10 +56,6 @@ class ClientHttpRequestFactoriesRuntimeHints implements RuntimeHintsRegistrar { typeHint.onReachableType(TypeReference.of(ClientHttpRequestFactories.APACHE_HTTP_CLIENT_CLASS)); registerReflectionHints(hints, HttpComponentsClientHttpRequestFactory.class); }); - hints.registerTypeIfPresent(classLoader, ClientHttpRequestFactories.OKHTTP_CLIENT_CLASS, (typeHint) -> { - typeHint.onReachableType(TypeReference.of(ClientHttpRequestFactories.OKHTTP_CLIENT_CLASS)); - registerReflectionHints(hints, OkHttp3ClientHttpRequestFactory.class); - }); hints.registerTypeIfPresent(classLoader, ClientHttpRequestFactories.JETTY_CLIENT_CLASS, (typeHint) -> { typeHint.onReachableType(TypeReference.of(ClientHttpRequestFactories.JETTY_CLIENT_CLASS)); registerReflectionHints(hints, JettyClientHttpRequestFactory.class, long.class); @@ -68,6 +64,17 @@ class ClientHttpRequestFactoriesRuntimeHints implements RuntimeHintsRegistrar { typeHint.onReachableType(HttpURLConnection.class); registerReflectionHints(hints, SimpleClientHttpRequestFactory.class); }); + registerOkHttpHints(hints, classLoader); + } + + @SuppressWarnings("removal") + @Deprecated(since = "3.2.0", forRemoval = true) + private void registerOkHttpHints(ReflectionHints hints, ClassLoader classLoader) { + hints.registerTypeIfPresent(classLoader, ClientHttpRequestFactories.OKHTTP_CLIENT_CLASS, (typeHint) -> { + typeHint.onReachableType(TypeReference.of(ClientHttpRequestFactories.OKHTTP_CLIENT_CLASS)); + registerReflectionHints(hints, OkHttp3ClientHttpRequestFactory.class); + }); + } private void registerReflectionHints(ReflectionHints hints, diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp3Tests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp3Tests.java index b54e8050d2..ad414d38f2 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp3Tests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp3Tests.java @@ -35,7 +35,9 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Andy Wilkinson */ @ClassPathOverrides("com.squareup.okhttp3:okhttp:3.14.9") -@ClassPathExclusions("httpclient5-*.jar") +@ClassPathExclusions({ "httpclient5-*.jar", "jetty-client-*.jar" }) +@Deprecated(since = "3.2.0") +@SuppressWarnings("removal") class ClientHttpRequestFactoriesOkHttp3Tests extends AbstractClientHttpRequestFactoriesTests { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp4Tests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp4Tests.java index d1f533f731..f6241c7a41 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp4Tests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp4Tests.java @@ -33,7 +33,9 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Andy Wilkinson */ -@ClassPathExclusions("httpclient5-*.jar") +@ClassPathExclusions({ "httpclient5-*.jar", "jetty-client-*.jar" }) +@Deprecated(since = "3.2.0") +@SuppressWarnings("removal") class ClientHttpRequestFactoriesOkHttp4Tests extends AbstractClientHttpRequestFactoriesTests { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHintsTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHintsTests.java index 7eee323f11..1fc41a8e29 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHintsTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHintsTests.java @@ -63,6 +63,8 @@ class ClientHttpRequestFactoriesRuntimeHintsTests { } @Test + @Deprecated(since = "3.2.0") + @SuppressWarnings("removal") void shouldRegisterOkHttpHints() { RuntimeHints hints = new RuntimeHints(); new ClientHttpRequestFactoriesRuntimeHints().registerHints(hints, getClass().getClassLoader()); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesTests.java index 239023e8b9..ff27a20d03 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesTests.java @@ -70,6 +70,8 @@ class ClientHttpRequestFactoriesTests { } @Test + @Deprecated(since = "3.2.0") + @SuppressWarnings("removal") void getOfOkHttpFactoryReturnsOkHttpFactory() { ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(OkHttp3ClientHttpRequestFactory.class, ClientHttpRequestFactorySettings.DEFAULTS); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderOkHttp3IntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderOkHttp3IntegrationTests.java index e48feb9be0..3215dad499 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderOkHttp3IntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderOkHttp3IntegrationTests.java @@ -18,12 +18,12 @@ package org.springframework.boot.webservices.client; import java.time.Duration; -import okhttp3.OkHttpClient; +import org.eclipse.jetty.client.HttpClient; import org.junit.jupiter.api.Test; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; +import org.springframework.http.client.JettyClientHttpRequestFactory; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.ws.transport.WebServiceMessageSender; import org.springframework.ws.transport.http.ClientHttpRequestMessageSender; @@ -42,9 +42,9 @@ class HttpWebServiceMessageSenderBuilderOkHttp3IntegrationTests { private final HttpWebServiceMessageSenderBuilder builder = new HttpWebServiceMessageSenderBuilder(); @Test - void buildUseOkHttp3ByDefault() { + void buildUseJettyClientIfHttpComponentsIsNotAvailable() { WebServiceMessageSender messageSender = this.builder.build(); - assertOkHttp3RequestFactory(messageSender); + assertJettyClientHttpRequestFactory(messageSender); } @Test @@ -52,19 +52,19 @@ class HttpWebServiceMessageSenderBuilderOkHttp3IntegrationTests { WebServiceMessageSender messageSender = this.builder.setConnectTimeout(Duration.ofSeconds(5)) .setReadTimeout(Duration.ofSeconds(2)) .build(); - OkHttp3ClientHttpRequestFactory factory = assertOkHttp3RequestFactory(messageSender); - OkHttpClient client = (OkHttpClient) ReflectionTestUtils.getField(factory, "client"); + JettyClientHttpRequestFactory factory = assertJettyClientHttpRequestFactory(messageSender); + HttpClient client = (HttpClient) ReflectionTestUtils.getField(factory, "httpClient"); assertThat(client).isNotNull(); - assertThat(client.connectTimeoutMillis()).isEqualTo(5000); - assertThat(client.readTimeoutMillis()).isEqualTo(2000); + assertThat(client.getConnectTimeout()).isEqualTo(5000); + assertThat(factory).hasFieldOrPropertyWithValue("readTimeout", 2000L); } - private OkHttp3ClientHttpRequestFactory assertOkHttp3RequestFactory(WebServiceMessageSender messageSender) { + private JettyClientHttpRequestFactory assertJettyClientHttpRequestFactory(WebServiceMessageSender messageSender) { assertThat(messageSender).isInstanceOf(ClientHttpRequestMessageSender.class); ClientHttpRequestMessageSender sender = (ClientHttpRequestMessageSender) messageSender; ClientHttpRequestFactory requestFactory = sender.getRequestFactory(); - assertThat(requestFactory).isInstanceOf(OkHttp3ClientHttpRequestFactory.class); - return (OkHttp3ClientHttpRequestFactory) requestFactory; + assertThat(requestFactory).isInstanceOf(JettyClientHttpRequestFactory.class); + return (JettyClientHttpRequestFactory) requestFactory; } } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/build.gradle index e66d4db03d..fba550646e 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/build.gradle +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/build.gradle @@ -13,9 +13,9 @@ dependencies { testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test")) testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation(project(":spring-boot-project:spring-boot-testcontainers")) - testImplementation("com.squareup.okhttp3:okhttp") testImplementation("io.projectreactor:reactor-core") testImplementation("io.projectreactor:reactor-test") + testImplementation("org.apache.httpcomponents.client5:httpclient5") testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.junit.platform:junit-platform-engine") testImplementation("org.junit.platform:junit-platform-launcher") diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/test/java/smoketest/data/couchbase/SecureCouchbaseContainer.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/test/java/smoketest/data/couchbase/SecureCouchbaseContainer.java index 1b40442edc..70141b4540 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/test/java/smoketest/data/couchbase/SecureCouchbaseContainer.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/test/java/smoketest/data/couchbase/SecureCouchbaseContainer.java @@ -17,13 +17,14 @@ package smoketest.data.couchbase; import java.time.Duration; +import java.util.Base64; import com.github.dockerjava.api.command.InspectContainerResponse; -import okhttp3.Credentials; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.testcontainers.couchbase.CouchbaseContainer; import org.testcontainers.utility.MountableFile; @@ -33,6 +34,7 @@ import org.springframework.boot.testsupport.testcontainers.DockerImageNames; * A {@link CouchbaseContainer} for Couchbase with SSL configuration. * * @author Scott Frederick + * @author Stephane Nicoll */ public class SecureCouchbaseContainer extends CouchbaseContainer { @@ -69,20 +71,26 @@ public class SecureCouchbaseContainer extends CouchbaseContainer { } private void doHttpRequest(String path) { - Response response; - try { + HttpResponse response = post(path); + if (response.getCode() != 200) { + throw new IllegalStateException("Error calling Couchbase HTTP endpoint: " + response); + } + } + + private HttpResponse post(String path) { + try (CloseableHttpClient httpclient = HttpClients.createDefault()) { + String basicAuth = "Basic " + + Base64.getEncoder().encodeToString("%s:%s".formatted(ADMIN_USER, ADMIN_PASSWORD).getBytes()); String url = "http://%s:%d/%s".formatted(getHost(), getMappedPort(MANAGEMENT_PORT), path); - Request.Builder requestBuilder = new Request.Builder().url(url) - .header("Authorization", Credentials.basic(ADMIN_USER, ADMIN_PASSWORD)) - .post(RequestBody.create("".getBytes())); - response = new OkHttpClient().newCall(requestBuilder.build()).execute(); + ClassicHttpRequest httpPost = ClassicRequestBuilder.post(url) + .addHeader("Authorization", basicAuth) + .setEntity("") + .build(); + return httpclient.execute(httpPost, (response) -> response); } catch (Exception ex) { throw new IllegalStateException("Error calling Couchbase HTTP endpoint", ex); } - if (!response.isSuccessful()) { - throw new IllegalStateException("Error calling Couchbase HTTP endpoint: " + response); - } } }