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
This commit is contained in:
@@ -280,6 +280,11 @@
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-reactive-httpclient</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>javax-websocket-server-impl</artifactId>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) -> {
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user