Integrate Apache http client with WebClient
This commit introduces a ClientHttpConnector implementation backed by Apache HttpComponents HttpClient 5.0. Fixes gh-24700
This commit is contained in:
committed by
Arjen Poutsma
parent
859953fe81
commit
3bc1d42dcd
@@ -25,6 +25,7 @@ import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.JettyClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.http.codec.ClientCodecConfigurer;
|
||||
@@ -50,10 +51,14 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
|
||||
|
||||
private static final boolean jettyClientPresent;
|
||||
|
||||
private static final boolean httpComponentsClientPresent;
|
||||
|
||||
static {
|
||||
ClassLoader loader = DefaultWebClientBuilder.class.getClassLoader();
|
||||
reactorClientPresent = ClassUtils.isPresent("reactor.netty.http.client.HttpClient", loader);
|
||||
jettyClientPresent = ClassUtils.isPresent("org.eclipse.jetty.client.HttpClient", loader);
|
||||
httpComponentsClientPresent = ClassUtils.isPresent("org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient", loader)
|
||||
&& ClassUtils.isPresent("org.apache.hc.core5.reactive.ReactiveDataConsumer", loader);
|
||||
}
|
||||
|
||||
|
||||
@@ -275,6 +280,9 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
|
||||
else if (jettyClientPresent) {
|
||||
return new JettyClientHttpConnector();
|
||||
}
|
||||
else if (httpComponentsClientPresent) {
|
||||
return new HttpComponentsClientHttpConnector();
|
||||
}
|
||||
throw new IllegalStateException("No suitable default ClientHttpConnector found");
|
||||
}
|
||||
|
||||
|
||||
@@ -58,8 +58,10 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.JettyClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.testfixture.xml.Pojo;
|
||||
@@ -74,6 +76,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Denys Ivano
|
||||
* @author Sebastien Deleuze
|
||||
* @author Sam Brannen
|
||||
* @author Martin Tarjányi
|
||||
*/
|
||||
class WebClientIntegrationTests {
|
||||
|
||||
@@ -85,7 +88,11 @@ class WebClientIntegrationTests {
|
||||
}
|
||||
|
||||
static Stream<ClientHttpConnector> arguments() {
|
||||
return Stream.of(new JettyClientHttpConnector(), new ReactorClientHttpConnector());
|
||||
return Stream.of(
|
||||
new ReactorClientHttpConnector(),
|
||||
new JettyClientHttpConnector(),
|
||||
new HttpComponentsClientHttpConnector()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +120,10 @@ class WebClientIntegrationTests {
|
||||
void retrieve(ClientHttpConnector connector) {
|
||||
startServer(connector);
|
||||
|
||||
prepareResponse(response -> response.setBody("Hello Spring!"));
|
||||
prepareResponse(response -> response.setHeader("Content-Type", "text/plain")
|
||||
.addHeader("Set-Cookie", "testkey1=testvalue1;")
|
||||
.addHeader("Set-Cookie", "testkey2=testvalue2; Max-Age=42; HttpOnly; Secure")
|
||||
.setBody("Hello Spring!"));
|
||||
|
||||
Mono<String> result = this.webClient.get()
|
||||
.uri("/greeting")
|
||||
@@ -1079,6 +1089,42 @@ class WebClientIntegrationTests {
|
||||
expectRequestCount(2);
|
||||
}
|
||||
|
||||
@ParameterizedWebClientTest
|
||||
void exchangeResponseCookies(ClientHttpConnector connector) {
|
||||
startServer(connector);
|
||||
|
||||
prepareResponse(response -> response
|
||||
.setHeader("Content-Type", "text/plain")
|
||||
.addHeader("Set-Cookie", "testkey1=testvalue1;")
|
||||
.addHeader("Set-Cookie", "testkey2=testvalue2; Max-Age=42; HttpOnly; Secure")
|
||||
.setBody("test"));
|
||||
|
||||
Mono<ClientResponse> result = this.webClient.get()
|
||||
.uri("/test")
|
||||
.exchange();
|
||||
|
||||
StepVerifier.create(result)
|
||||
.consumeNextWith(response -> {
|
||||
assertThat(response.cookies()).containsOnlyKeys("testkey1", "testkey2");
|
||||
|
||||
ResponseCookie cookie1 = response.cookies().get("testkey1").get(0);
|
||||
assertThat(cookie1.getValue()).isEqualTo("testvalue1");
|
||||
assertThat(cookie1.isSecure()).isFalse();
|
||||
assertThat(cookie1.isHttpOnly()).isFalse();
|
||||
assertThat(cookie1.getMaxAge().getSeconds()).isEqualTo(-1);
|
||||
|
||||
ResponseCookie cookie2 = response.cookies().get("testkey2").get(0);
|
||||
assertThat(cookie2.getValue()).isEqualTo("testvalue2");
|
||||
assertThat(cookie2.isSecure()).isTrue();
|
||||
assertThat(cookie2.isHttpOnly()).isTrue();
|
||||
assertThat(cookie2.getMaxAge().getSeconds()).isEqualTo(42);
|
||||
})
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3));
|
||||
|
||||
expectRequestCount(1);
|
||||
}
|
||||
|
||||
|
||||
private void prepareResponse(Consumer<MockResponse> consumer) {
|
||||
MockResponse response = new MockResponse();
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.JettyClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
@@ -73,12 +74,16 @@ class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
|
||||
return new Object[][] {
|
||||
{new JettyHttpServer(), new ReactorClientHttpConnector()},
|
||||
{new JettyHttpServer(), new JettyClientHttpConnector()},
|
||||
{new JettyHttpServer(), new HttpComponentsClientHttpConnector()},
|
||||
{new ReactorHttpServer(), new ReactorClientHttpConnector()},
|
||||
{new ReactorHttpServer(), new JettyClientHttpConnector()},
|
||||
{new ReactorHttpServer(), new HttpComponentsClientHttpConnector()},
|
||||
{new TomcatHttpServer(), new ReactorClientHttpConnector()},
|
||||
{new TomcatHttpServer(), new JettyClientHttpConnector()},
|
||||
{new TomcatHttpServer(), new HttpComponentsClientHttpConnector()},
|
||||
{new UndertowHttpServer(), new ReactorClientHttpConnector()},
|
||||
{new UndertowHttpServer(), new JettyClientHttpConnector()}
|
||||
{new UndertowHttpServer(), new JettyClientHttpConnector()},
|
||||
{new UndertowHttpServer(), new HttpComponentsClientHttpConnector()}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user