Merge branch '5.3.x' into main
This commit is contained in:
commit
ccb080f948
|
@ -203,7 +203,7 @@ class DefaultClientResponse implements ClientResponse {
|
||||||
return bytes;
|
return bytes;
|
||||||
})
|
})
|
||||||
.defaultIfEmpty(EMPTY)
|
.defaultIfEmpty(EMPTY)
|
||||||
.onErrorReturn(IllegalStateException.class::isInstance, EMPTY)
|
.onErrorReturn(ex -> !(ex instanceof Error), EMPTY)
|
||||||
.map(bodyBytes -> {
|
.map(bodyBytes -> {
|
||||||
HttpRequest request = this.requestSupplier.get();
|
HttpRequest request = this.requestSupplier.get();
|
||||||
Charset charset = headers().contentType().map(MimeType::getCharset).orElse(null);
|
Charset charset = headers().contentType().map(MimeType::getCharset).orElse(null);
|
||||||
|
|
|
@ -612,7 +612,9 @@ class DefaultWebClient implements WebClient {
|
||||||
public Mono<ResponseEntity<Void>> toBodilessEntity() {
|
public Mono<ResponseEntity<Void>> toBodilessEntity() {
|
||||||
return this.responseMono.flatMap(response ->
|
return this.responseMono.flatMap(response ->
|
||||||
WebClientUtils.mapToEntity(response, handleBodyMono(response, Mono.<Void>empty()))
|
WebClientUtils.mapToEntity(response, handleBodyMono(response, Mono.<Void>empty()))
|
||||||
.flatMap(entity -> response.releaseBody().thenReturn(entity))
|
.flatMap(entity -> response.releaseBody()
|
||||||
|
.onErrorResume(WebClientUtils.WRAP_EXCEPTION_PREDICATE, exceptionWrappingFunction(response))
|
||||||
|
.thenReturn(entity))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,15 @@ package org.springframework.web.reactive.function.client;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -31,6 +35,7 @@ import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@ -64,7 +69,9 @@ import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||||
import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector;
|
import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector;
|
||||||
import org.springframework.http.client.reactive.JettyClientHttpConnector;
|
import org.springframework.http.client.reactive.JettyClientHttpConnector;
|
||||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||||
|
import org.springframework.util.SocketUtils;
|
||||||
import org.springframework.web.reactive.function.BodyExtractors;
|
import org.springframework.web.reactive.function.BodyExtractors;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient.ResponseSpec;
|
||||||
import org.springframework.web.testfixture.xml.Pojo;
|
import org.springframework.web.testfixture.xml.Pojo;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
@ -83,7 +90,7 @@ class WebClientIntegrationTests {
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
@ParameterizedTest(name = "[{index}] webClient [{0}]")
|
@ParameterizedTest(name = "[{index}] {displayName} [{0}]")
|
||||||
@MethodSource("arguments")
|
@MethodSource("arguments")
|
||||||
@interface ParameterizedWebClientTest {
|
@interface ParameterizedWebClientTest {
|
||||||
}
|
}
|
||||||
|
@ -113,7 +120,9 @@ class WebClientIntegrationTests {
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
void shutdown() throws IOException {
|
void shutdown() throws IOException {
|
||||||
this.server.shutdown();
|
if (server != null) {
|
||||||
|
this.server.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1209,6 +1218,65 @@ class WebClientIntegrationTests {
|
||||||
.verify();
|
.verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ParameterizedWebClientTest
|
||||||
|
void malformedResponseChunksOnBodilessEntity(ClientHttpConnector connector) {
|
||||||
|
Mono<?> result = doMalformedChunkedResponseTest(connector, ResponseSpec::toBodilessEntity);
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectErrorSatisfies(throwable -> {
|
||||||
|
assertThat(throwable).isInstanceOf(WebClientException.class);
|
||||||
|
WebClientException ex = (WebClientException) throwable;
|
||||||
|
assertThat(ex.getCause()).isInstanceOf(IOException.class);
|
||||||
|
})
|
||||||
|
.verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedWebClientTest
|
||||||
|
void malformedResponseChunksOnEntityWithBody(ClientHttpConnector connector) {
|
||||||
|
Mono<?> result = doMalformedChunkedResponseTest(connector, spec -> spec.toEntity(String.class));
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectErrorSatisfies(throwable -> {
|
||||||
|
assertThat(throwable).isInstanceOf(WebClientException.class);
|
||||||
|
WebClientException ex = (WebClientException) throwable;
|
||||||
|
assertThat(ex.getCause()).isInstanceOf(IOException.class);
|
||||||
|
})
|
||||||
|
.verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> Mono<T> doMalformedChunkedResponseTest(
|
||||||
|
ClientHttpConnector connector, Function<ResponseSpec, Mono<T>> handler) {
|
||||||
|
|
||||||
|
int port = SocketUtils.findAvailableTcpPort();
|
||||||
|
|
||||||
|
Thread serverThread = new Thread(() -> {
|
||||||
|
// No way to simulate a malformed chunked response through MockWebServer.
|
||||||
|
try (ServerSocket serverSocket = new ServerSocket(port)) {
|
||||||
|
Socket socket = serverSocket.accept();
|
||||||
|
InputStream is = socket.getInputStream();
|
||||||
|
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
is.read(new byte[4096]);
|
||||||
|
|
||||||
|
OutputStream os = socket.getOutputStream();
|
||||||
|
os.write("HTTP/1.1 200 OK\r\n".getBytes(StandardCharsets.UTF_8));
|
||||||
|
os.write("Transfer-Encoding: chunked\r\n".getBytes(StandardCharsets.UTF_8));
|
||||||
|
os.write("\r\n".getBytes(StandardCharsets.UTF_8));
|
||||||
|
os.write("lskdu018973t09sylgasjkfg1][]'./.sdlv".getBytes(StandardCharsets.UTF_8));
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
serverThread.start();
|
||||||
|
|
||||||
|
WebClient client = WebClient.builder()
|
||||||
|
.clientConnector(connector)
|
||||||
|
.baseUrl("http://localhost:" + port)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return handler.apply(client.post().retrieve());
|
||||||
|
}
|
||||||
|
|
||||||
private void prepareResponse(Consumer<MockResponse> consumer) {
|
private void prepareResponse(Consumer<MockResponse> consumer) {
|
||||||
MockResponse response = new MockResponse();
|
MockResponse response = new MockResponse();
|
||||||
|
@ -1252,5 +1320,4 @@ class WebClientIntegrationTests {
|
||||||
this.containerValue = containerValue;
|
this.containerValue = containerValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue