Add ClientHttpConnector test suite
This commit introduces a test suite for ClientHttpConnector implementations, as well as fixes that resolve issues identified by these tests. Closes gh-24941
This commit is contained in:
@@ -27,6 +27,7 @@ import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.core5.concurrent.FutureCallback;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
import org.apache.hc.core5.http.HttpStreamResetException;
|
||||
import org.apache.hc.core5.http.Message;
|
||||
import org.apache.hc.core5.http.nio.AsyncRequestProducer;
|
||||
import org.apache.hc.core5.reactive.ReactiveResponseConsumer;
|
||||
@@ -43,6 +44,7 @@ import org.springframework.util.Assert;
|
||||
* {@link ClientHttpConnector} implementation for the Apache HttpComponents HttpClient 5.x.
|
||||
*
|
||||
* @author Martin Tarjányi
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.3
|
||||
* @see <a href="https://hc.apache.org/index.html">Apache HttpComponents</a>
|
||||
*/
|
||||
@@ -148,7 +150,12 @@ public class HttpComponentsClientHttpConnector implements ClientHttpConnector {
|
||||
|
||||
@Override
|
||||
public void failed(Exception ex) {
|
||||
this.sink.error(ex);
|
||||
Throwable t = ex;
|
||||
if (t instanceof HttpStreamResetException) {
|
||||
HttpStreamResetException httpStreamResetException = (HttpStreamResetException) ex;
|
||||
t = httpStreamResetException.getCause();
|
||||
}
|
||||
this.sink.error(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.hc.client5.http.cookie.CookieStore;
|
||||
import org.apache.hc.client5.http.impl.cookie.BasicClientCookie;
|
||||
@@ -39,13 +40,14 @@ import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.http.MediaType.ALL_VALUE;
|
||||
|
||||
/**
|
||||
* {@link ClientHttpRequest} implementation for the Apache HttpComponents HttpClient 5.x.
|
||||
*
|
||||
* @author Martin Tarjányi
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.3
|
||||
* @see <a href="https://hc.apache.org/index.html">Apache HttpComponents</a>
|
||||
*/
|
||||
@@ -72,7 +74,9 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest {
|
||||
|
||||
@Override
|
||||
public HttpMethod getMethod() {
|
||||
return HttpMethod.resolve(this.httpRequest.getMethod());
|
||||
HttpMethod method = HttpMethod.resolve(this.httpRequest.getMethod());
|
||||
Assert.state(method != null, "Method must not be null");
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,7 +104,7 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest {
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
|
||||
return writeWith(Flux.from(body).flatMap(p -> p));
|
||||
return writeWith(Flux.from(body).flatMap(Function.identity()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.springframework.util.MultiValueMap;
|
||||
* {@link ClientHttpResponse} implementation for the Apache HttpComponents HttpClient 5.x.
|
||||
*
|
||||
* @author Martin Tarjányi
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.3
|
||||
* @see <a href="https://hc.apache.org/index.html">Apache HttpComponents</a>
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -91,12 +91,13 @@ public class JettyClientHttpConnector implements ClientHttpConnector {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the buffer factory to be used.
|
||||
*/
|
||||
public void setBufferFactory(DataBufferFactory bufferFactory) {
|
||||
this.bufferFactory = bufferFactory;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<ClientHttpResponse> connect(HttpMethod method, URI uri,
|
||||
Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
|
||||
@@ -125,16 +126,7 @@ public class JettyClientHttpConnector implements ClientHttpConnector {
|
||||
}
|
||||
|
||||
private DataBuffer toDataBuffer(ContentChunk chunk) {
|
||||
|
||||
// We must copy until this is resolved:
|
||||
// https://github.com/eclipse/jetty.project/issues/2429
|
||||
|
||||
// Use copy instead of buffer wrapping because Callback#succeeded() is
|
||||
// used not only to release the buffer but also to request more data
|
||||
// which is a problem for codecs that buffer data.
|
||||
|
||||
DataBuffer buffer = this.bufferFactory.allocateBuffer(chunk.buffer.capacity());
|
||||
buffer.write(chunk.buffer);
|
||||
DataBuffer buffer = this.bufferFactory.wrap(chunk.buffer);
|
||||
chunk.callback.succeeded();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,14 +26,13 @@ import org.eclipse.jetty.reactive.client.ContentChunk;
|
||||
import org.eclipse.jetty.reactive.client.ReactiveRequest;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.Exceptions;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.MonoSink;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.PooledDataBuffer;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -87,21 +86,18 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest {
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
|
||||
Flux<ContentChunk> chunks = Flux.from(body).map(this::toContentChunk);
|
||||
ReactiveRequest.Content content = ReactiveRequest.Content.fromPublisher(chunks, getContentType());
|
||||
this.reactiveRequest = ReactiveRequest.newBuilder(this.jettyRequest).content(content).build();
|
||||
return doCommit(this::completes);
|
||||
return Mono.<Void>create(sink -> {
|
||||
Flux<ContentChunk> chunks = Flux.from(body).map(buffer -> toContentChunk(buffer, sink));
|
||||
ReactiveRequest.Content content = ReactiveRequest.Content.fromPublisher(chunks, getContentType());
|
||||
this.reactiveRequest = ReactiveRequest.newBuilder(this.jettyRequest).content(content).build();
|
||||
sink.success();
|
||||
})
|
||||
.then(doCommit(this::completes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
|
||||
Flux<ContentChunk> chunks = Flux.from(body)
|
||||
.flatMap(Function.identity())
|
||||
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)
|
||||
.map(this::toContentChunk);
|
||||
ReactiveRequest.Content content = ReactiveRequest.Content.fromPublisher(chunks, getContentType());
|
||||
this.reactiveRequest = ReactiveRequest.newBuilder(this.jettyRequest).content(content).build();
|
||||
return doCommit(this::completes);
|
||||
return writeWith(Flux.from(body).flatMap(Function.identity()));
|
||||
}
|
||||
|
||||
private String getContentType() {
|
||||
@@ -113,7 +109,7 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
private ContentChunk toContentChunk(DataBuffer buffer) {
|
||||
private ContentChunk toContentChunk(DataBuffer buffer, MonoSink<Void> sink) {
|
||||
return new ContentChunk(buffer.asByteBuffer(), new Callback() {
|
||||
@Override
|
||||
public void succeeded() {
|
||||
@@ -123,7 +119,7 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest {
|
||||
@Override
|
||||
public void failed(Throwable x) {
|
||||
DataBufferUtils.release(buffer);
|
||||
throw Exceptions.propagate(x);
|
||||
sink.error(x);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user