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:
martin-tarjanyi
2020-03-14 23:27:01 +01:00
committed by Arjen Poutsma
parent 859953fe81
commit 3bc1d42dcd
14 changed files with 537 additions and 8 deletions

View File

@@ -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");
}

View File

@@ -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();

View File

@@ -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()}
};
}