From 53f3982748da6c5f4f98bbb1cacf26f368f74986 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 23 Aug 2018 10:51:43 +0200 Subject: [PATCH] Support Jetty RS as HTTP driver for WebClient This commit adds a new auto-configuration choice for `ClientHttpConnector`, this time using the Jetty RS HTTP client library if available. This is the best choice in case the application runs on a Jetty reactive server, as both client and server will share resources. Closes gh-14005 --- .../spring-boot-autoconfigure/pom.xml | 5 ++ .../ClientHttpConnectorAutoConfiguration.java | 3 +- .../ClientHttpConnectorConfiguration.java | 22 +++++++++ ...ntHttpConnectorAutoConfigurationTests.java | 48 +++++++++++++++++++ .../main/asciidoc/spring-boot-features.adoc | 6 +++ 5 files changed, 83 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/pom.xml b/spring-boot-project/spring-boot-autoconfigure/pom.xml index da28b3c8b4..59de46a536 100755 --- a/spring-boot-project/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-autoconfigure/pom.xml @@ -280,6 +280,11 @@ jetty-webapp true + + org.eclipse.jetty + jetty-reactive-httpclient + true + org.eclipse.jetty.websocket javax-websocket-server-impl diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java index 828970fd5e..e25157fc00 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java @@ -39,7 +39,8 @@ import org.springframework.web.reactive.function.client.WebClient; */ @Configuration @ConditionalOnClass(WebClient.class) -@Import({ ClientHttpConnectorConfiguration.ReactorNetty.class }) +@Import({ ClientHttpConnectorConfiguration.ReactorNetty.class, + ClientHttpConnectorConfiguration.JettyClient.class }) public class ClientHttpConnectorAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java index 1997e8b1f7..78493c20b7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java @@ -23,6 +23,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.http.client.reactive.JettyClientHttpConnector; +import org.springframework.http.client.reactive.JettyResourceFactory; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.http.client.reactive.ReactorResourceFactory; @@ -59,4 +61,24 @@ class ClientHttpConnectorConfiguration { } + @Configuration + @ConditionalOnClass(org.eclipse.jetty.reactive.client.ReactiveRequest.class) + @ConditionalOnMissingBean(ClientHttpConnector.class) + public static class JettyClient { + + @Bean + @ConditionalOnMissingBean + public JettyResourceFactory jettyResourceFactory() { + return new JettyResourceFactory(); + } + + @Bean + public JettyClientHttpConnector jettyClientHttpConnector( + JettyResourceFactory jettyResourceFactory) { + return new JettyClientHttpConnector(jettyResourceFactory, (httpClient) -> { + }); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java index 844d88b1af..56fad97f65 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java @@ -21,6 +21,9 @@ import org.junit.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.http.client.reactive.ReactorResourceFactory; import org.springframework.web.reactive.function.client.WebClient; @@ -56,4 +59,49 @@ public class ClientHttpConnectorAutoConfigurationTests { }); } + @Test + public void shouldNotOverrideCustomClientConnector() { + this.contextRunner.withUserConfiguration(CustomClientHttpConnectorConfig.class) + .run((context) -> { + assertThat(context).hasSingleBean(ClientHttpConnector.class) + .hasBean("customConnector") + .doesNotHaveBean(ReactorResourceFactory.class); + WebClientCustomizer clientCustomizer = context + .getBean(WebClientCustomizer.class); + WebClient.Builder builder = mock(WebClient.Builder.class); + clientCustomizer.customize(builder); + verify(builder, times(1)) + .clientConnector(any(ClientHttpConnector.class)); + }); + } + + @Test + public void shouldUseCustomReactorResourceFactory() { + this.contextRunner.withUserConfiguration(CustomReactorResourceConfig.class) + .run((context) -> assertThat(context) + .hasSingleBean(ReactorClientHttpConnector.class) + .hasSingleBean(ReactorResourceFactory.class) + .hasBean("customReactorResourceFactory")); + } + + @Configuration + static class CustomClientHttpConnectorConfig { + + @Bean + public ClientHttpConnector customConnector() { + return mock(ClientHttpConnector.class); + } + + } + + @Configuration + static class CustomReactorResourceConfig { + + @Bean + public ReactorResourceFactory customReactorResourceFactory() { + return new ReactorResourceFactory(); + } + + } + } diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 5cf26f2298..820790838b 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -5822,6 +5822,12 @@ The following code shows a typical example: Spring Boot will auto-detect which `ClientHttpConnector` to drive `WebClient`, depending on the libraries available on the application classpath. +The `spring-boot-starter-webflux` depends on `io.projectreactor.netty:reactor-netty` by +default, which brings both server and client implementations. If you choose to use Jetty +as a reactive server instead, you should add a dependency on the Jetty Reactive HTTP +client library, `org.eclipse.jetty:jetty-reactive-httpclient`, because it will +automatically share HTTP resources with the server. + Developers can override this choice by defining their own `ClientHttpConnector` bean; in this case, and depending on your HTTP client library of choice, you should also define a resource factory bean that manages the HTTP resources for that client.