Improve unknown status codes handling by WebClient
Prior to this commit, `WebClient` would throw `IllegalArgumentException`
when receiving an HTTP response with an unknown HTTP status code.
This commit is a follow up of SPR-16748 (supporting non-standard HTTP
status codes on the reactive `ClientHttpResponse`), and is mirroring
SPR-15978 (supporting non-standard HTTP status codes in `RestTemplate`).
With this change, `WebClient` now tolerates unknown status codes in some
cases, while not offering that choice as a first class citizen:
`HttpStatus` is still the preferred way to deal with HTTP status codes.
Here's how `WebClient` will behave when fetching the full response:
```
// Given a remote endpoint returning a "123" HTTP status code
Mono<ClientResponse> result = this.webClient.get()
.uri("/status/123")
.exchange();
// will still throw an IllegalArgumentException
HttpStatus status = result.block().statusCode();
// is safe and will return 123
int statusCode = result.block().rawStatusCode();
```
Resolving directly the response body with `retrieve()` is different.
```
// will send an error signal with a UnknownHttpStatusCodeException
Mono<String> result = this.webClient.get()
.uri("/status/123")
.retrieve()
.bodyToMono(String.class);
```
In general, `WebClient` will provide high-level support
for well-known HTTP status codes, like error handling with
`WebClient.ResponseSpec#onStatus`.
For such support with unknown status codes, it is better to rely
on lower level constructs such as `ExchangeFilterFunction`.
Issue: SPR-16819
This commit is contained in:
parent
f123d6c3bc
commit
038af9a303
|
|
@ -60,9 +60,21 @@ public interface ClientResponse {
|
|||
|
||||
/**
|
||||
* Return the status code of this response.
|
||||
* @return the status as an HttpStatus enum value
|
||||
* @throws IllegalArgumentException in case of an unknown HTTP status code
|
||||
* @see HttpStatus#valueOf(int)
|
||||
*/
|
||||
HttpStatus statusCode();
|
||||
|
||||
/**
|
||||
* Return the (potentially non-standard) status code of this response.
|
||||
* @return the status as an integer
|
||||
* @since 5.1
|
||||
* @see #statusCode()
|
||||
* @see HttpStatus#resolve(int)
|
||||
*/
|
||||
int rawStatusCode();
|
||||
|
||||
/**
|
||||
* Return the headers of this response.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -76,6 +76,11 @@ class DefaultClientResponse implements ClientResponse {
|
|||
return this.response.getStatusCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int rawStatusCode() {
|
||||
return this.response.getRawStatusCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Headers headers() {
|
||||
return this.headers;
|
||||
|
|
@ -177,11 +182,11 @@ class DefaultClientResponse implements ClientResponse {
|
|||
|
||||
private <T> Mono<ResponseEntity<T>> toEntityInternal(Mono<T> bodyMono) {
|
||||
HttpHeaders headers = headers().asHttpHeaders();
|
||||
HttpStatus statusCode = statusCode();
|
||||
int status = rawStatusCode();
|
||||
return bodyMono
|
||||
.map(body -> new ResponseEntity<>(body, headers, statusCode))
|
||||
.map(body -> createEntity(body, headers, status))
|
||||
.switchIfEmpty(Mono.defer(
|
||||
() -> Mono.just(new ResponseEntity<>(headers, statusCode))));
|
||||
() -> Mono.just(createEntity(headers, status))));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -196,10 +201,24 @@ class DefaultClientResponse implements ClientResponse {
|
|||
|
||||
private <T> Mono<ResponseEntity<List<T>>> toEntityListInternal(Flux<T> bodyFlux) {
|
||||
HttpHeaders headers = headers().asHttpHeaders();
|
||||
HttpStatus statusCode = statusCode();
|
||||
int status = rawStatusCode();
|
||||
return bodyFlux
|
||||
.collectList()
|
||||
.map(body -> new ResponseEntity<>(body, headers, statusCode));
|
||||
.map(body -> createEntity(body, headers, status));
|
||||
}
|
||||
|
||||
private <T> ResponseEntity<T> createEntity(HttpHeaders headers, int status) {
|
||||
HttpStatus resolvedStatus = HttpStatus.resolve(status);
|
||||
return resolvedStatus != null
|
||||
? new ResponseEntity<>(headers, resolvedStatus)
|
||||
: ResponseEntity.status(status).headers(headers).build();
|
||||
}
|
||||
|
||||
private <T> ResponseEntity<T> createEntity(T body, HttpHeaders headers, int status) {
|
||||
HttpStatus resolvedStatus = HttpStatus.resolve(status);
|
||||
return resolvedStatus != null
|
||||
? new ResponseEntity<>(body, headers, resolvedStatus)
|
||||
: ResponseEntity.status(status).headers(headers).body(body);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ import org.springframework.web.util.UriBuilderFactory;
|
|||
* Default implementation of {@link WebClient}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
* @since 5.0
|
||||
*/
|
||||
class DefaultWebClient implements WebClient {
|
||||
|
|
@ -375,7 +376,7 @@ class DefaultWebClient implements WebClient {
|
|||
|
||||
private final Mono<ClientResponse> responseMono;
|
||||
|
||||
private List<StatusHandler> statusHandlers = new ArrayList<>(1);
|
||||
private final List<StatusHandler> statusHandlers = new ArrayList<>(1);
|
||||
|
||||
DefaultResponseSpec(Mono<ClientResponse> responseMono) {
|
||||
this.responseMono = responseMono;
|
||||
|
|
@ -435,13 +436,17 @@ class DefaultWebClient implements WebClient {
|
|||
|
||||
private <T extends Publisher<?>> T bodyToPublisher(ClientResponse response,
|
||||
T bodyPublisher, Function<Mono<? extends Throwable>, T> errorFunction) {
|
||||
|
||||
return this.statusHandlers.stream()
|
||||
.filter(statusHandler -> statusHandler.test(response.statusCode()))
|
||||
.findFirst()
|
||||
.map(statusHandler -> statusHandler.apply(response))
|
||||
.map(errorFunction::apply)
|
||||
.orElse(bodyPublisher);
|
||||
if (HttpStatus.resolve(response.rawStatusCode()) != null) {
|
||||
return this.statusHandlers.stream()
|
||||
.filter(statusHandler -> statusHandler.test(response.statusCode()))
|
||||
.findFirst()
|
||||
.map(statusHandler -> statusHandler.apply(response))
|
||||
.map(errorFunction::apply)
|
||||
.orElse(bodyPublisher);
|
||||
}
|
||||
else {
|
||||
return errorFunction.apply(createResponseException(response));
|
||||
}
|
||||
}
|
||||
|
||||
private static Mono<WebClientResponseException> createResponseException(ClientResponse response) {
|
||||
|
|
@ -454,18 +459,26 @@ class DefaultWebClient implements WebClient {
|
|||
})
|
||||
.defaultIfEmpty(new byte[0])
|
||||
.map(bodyBytes -> {
|
||||
String msg = String.format("ClientResponse has erroneous status code: %d %s", response.statusCode().value(),
|
||||
response.statusCode().getReasonPhrase());
|
||||
Charset charset = response.headers().contentType()
|
||||
.map(MimeType::getCharset)
|
||||
.orElse(StandardCharsets.ISO_8859_1);
|
||||
return new WebClientResponseException(msg,
|
||||
response.statusCode().value(),
|
||||
response.statusCode().getReasonPhrase(),
|
||||
response.headers().asHttpHeaders(),
|
||||
bodyBytes,
|
||||
charset
|
||||
);
|
||||
if (HttpStatus.resolve(response.rawStatusCode()) != null) {
|
||||
String msg = String.format("ClientResponse has erroneous status code: %d %s",
|
||||
response.statusCode().value(), response.statusCode().getReasonPhrase());
|
||||
return new WebClientResponseException(msg,
|
||||
response.statusCode().value(),
|
||||
response.statusCode().getReasonPhrase(),
|
||||
response.headers().asHttpHeaders(),
|
||||
bodyBytes,
|
||||
charset);
|
||||
}
|
||||
else {
|
||||
return new UnknownHttpStatusCodeException(
|
||||
response.rawStatusCode(),
|
||||
response.headers().asHttpHeaders(),
|
||||
bodyBytes,
|
||||
charset);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package org.springframework.web.reactive.function.client;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
/**
|
||||
* Exception thrown when an unknown (or custom) HTTP status code is received.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 5.1
|
||||
*/
|
||||
public class UnknownHttpStatusCodeException extends WebClientResponseException {
|
||||
|
||||
private static final long serialVersionUID = 2407169540168185007L;
|
||||
|
||||
public UnknownHttpStatusCodeException(int statusCode, HttpHeaders headers,
|
||||
byte[] responseBody, Charset responseCharset) {
|
||||
super("Unknown status code [" + statusCode + "]", statusCode, "",
|
||||
headers, responseBody, responseCharset);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
|
|
@ -47,6 +47,7 @@ public class WebClientResponseException extends WebClientException {
|
|||
|
||||
/**
|
||||
* Construct a new instance of with the given response data.
|
||||
* @param message the exception message
|
||||
* @param statusCode the raw status code value
|
||||
* @param statusText the status text
|
||||
* @param headers the response headers (may be {@code null})
|
||||
|
|
@ -69,6 +70,7 @@ public class WebClientResponseException extends WebClientException {
|
|||
|
||||
/**
|
||||
* Return the HTTP status code value.
|
||||
* @throws IllegalArgumentException in case of an unknown HTTP status code
|
||||
*/
|
||||
public HttpStatus getStatusCode() {
|
||||
return HttpStatus.valueOf(this.statusCode);
|
||||
|
|
|
|||
|
|
@ -77,6 +77,11 @@ public class ClientResponseWrapper implements ClientResponse {
|
|||
return this.delegate.statusCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int rawStatusCode() {
|
||||
return this.delegate.rawStatusCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Headers headers() {
|
||||
return this.delegate.headers();
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ public interface HandlerFilterFunction<T extends ServerResponse, R extends Serve
|
|||
|
||||
/**
|
||||
* Adapt the given request processor function to a filter function that only operates
|
||||
* on the {@code ClientRequest}.
|
||||
* on the {@code ServerRequest}.
|
||||
* @param requestProcessor the request processor
|
||||
* @return the filter adaptation of the request processor
|
||||
*/
|
||||
|
|
@ -87,7 +87,7 @@ public interface HandlerFilterFunction<T extends ServerResponse, R extends Serve
|
|||
|
||||
/**
|
||||
* Adapt the given response processor function to a filter function that only operates
|
||||
* on the {@code ClientResponse}.
|
||||
* on the {@code ServerResponse}.
|
||||
* @param responseProcessor the response processor
|
||||
* @return the filter adaptation of the request processor
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
|
|
@ -54,6 +54,7 @@ import static org.springframework.web.reactive.function.BodyExtractors.*;
|
|||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
* @author Denys Ivano
|
||||
*/
|
||||
public class DefaultClientResponseTests {
|
||||
|
||||
|
|
@ -80,6 +81,14 @@ public class DefaultClientResponseTests {
|
|||
assertEquals(status, defaultClientResponse.statusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rawStatusCode() {
|
||||
int status = 999;
|
||||
when(mockResponse.getRawStatusCode()).thenReturn(status);
|
||||
|
||||
assertEquals(status, defaultClientResponse.rawStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void header() {
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
|
|
@ -143,6 +152,7 @@ public class DefaultClientResponseTests {
|
|||
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
|
||||
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
|
||||
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
|
||||
when(mockResponse.getBody()).thenReturn(body);
|
||||
|
||||
List<HttpMessageReader<?>> messageReaders = Collections
|
||||
|
|
@ -164,6 +174,7 @@ public class DefaultClientResponseTests {
|
|||
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
|
||||
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
|
||||
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
|
||||
when(mockResponse.getBody()).thenReturn(body);
|
||||
|
||||
List<HttpMessageReader<?>> messageReaders = Collections
|
||||
|
|
@ -187,6 +198,7 @@ public class DefaultClientResponseTests {
|
|||
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
|
||||
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
|
||||
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
|
||||
when(mockResponse.getBody()).thenReturn(body);
|
||||
|
||||
List<HttpMessageReader<?>> messageReaders = Collections
|
||||
|
|
@ -209,6 +221,7 @@ public class DefaultClientResponseTests {
|
|||
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
|
||||
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
|
||||
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
|
||||
when(mockResponse.getBody()).thenReturn(body);
|
||||
|
||||
List<HttpMessageReader<?>> messageReaders = Collections
|
||||
|
|
@ -233,6 +246,7 @@ public class DefaultClientResponseTests {
|
|||
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
|
||||
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
|
||||
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
|
||||
when(mockResponse.getBody()).thenReturn(body);
|
||||
|
||||
List<HttpMessageReader<?>> messageReaders = Collections
|
||||
|
|
@ -242,6 +256,37 @@ public class DefaultClientResponseTests {
|
|||
ResponseEntity<String> result = defaultClientResponse.toEntity(String.class).block();
|
||||
assertEquals("foo", result.getBody());
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals(HttpStatus.OK.value(), result.getStatusCodeValue());
|
||||
assertEquals(MediaType.TEXT_PLAIN, result.getHeaders().getContentType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toEntityWithUnknownStatusCode() throws Exception {
|
||||
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
|
||||
DefaultDataBuffer dataBuffer
|
||||
= factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
|
||||
Flux<DataBuffer> body = Flux.just(dataBuffer);
|
||||
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
|
||||
when(mockResponse.getStatusCode()).thenThrow(new IllegalArgumentException("999"));
|
||||
when(mockResponse.getRawStatusCode()).thenReturn(999);
|
||||
when(mockResponse.getBody()).thenReturn(body);
|
||||
|
||||
List<HttpMessageReader<?>> messageReaders = Collections
|
||||
.singletonList(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()));
|
||||
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders);
|
||||
|
||||
ResponseEntity<String> result = defaultClientResponse.toEntity(String.class).block();
|
||||
assertEquals("foo", result.getBody());
|
||||
try {
|
||||
result.getStatusCode();
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// do nothing
|
||||
}
|
||||
assertEquals(999, result.getStatusCodeValue());
|
||||
assertEquals(MediaType.TEXT_PLAIN, result.getHeaders().getContentType());
|
||||
}
|
||||
|
||||
|
|
@ -256,6 +301,7 @@ public class DefaultClientResponseTests {
|
|||
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
|
||||
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
|
||||
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
|
||||
when(mockResponse.getBody()).thenReturn(body);
|
||||
|
||||
List<HttpMessageReader<?>> messageReaders = Collections
|
||||
|
|
@ -267,6 +313,7 @@ public class DefaultClientResponseTests {
|
|||
}).block();
|
||||
assertEquals("foo", result.getBody());
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals(HttpStatus.OK.value(), result.getStatusCodeValue());
|
||||
assertEquals(MediaType.TEXT_PLAIN, result.getHeaders().getContentType());
|
||||
}
|
||||
|
||||
|
|
@ -281,6 +328,7 @@ public class DefaultClientResponseTests {
|
|||
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
|
||||
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
|
||||
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
|
||||
when(mockResponse.getBody()).thenReturn(body);
|
||||
|
||||
List<HttpMessageReader<?>> messageReaders = Collections
|
||||
|
|
@ -290,6 +338,37 @@ public class DefaultClientResponseTests {
|
|||
ResponseEntity<List<String>> result = defaultClientResponse.toEntityList(String.class).block();
|
||||
assertEquals(Collections.singletonList("foo"), result.getBody());
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals(HttpStatus.OK.value(), result.getStatusCodeValue());
|
||||
assertEquals(MediaType.TEXT_PLAIN, result.getHeaders().getContentType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toEntityListWithUnknownStatusCode() throws Exception {
|
||||
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
|
||||
DefaultDataBuffer dataBuffer =
|
||||
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
|
||||
Flux<DataBuffer> body = Flux.just(dataBuffer);
|
||||
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
|
||||
when(mockResponse.getStatusCode()).thenThrow(new IllegalArgumentException("999"));
|
||||
when(mockResponse.getRawStatusCode()).thenReturn(999);
|
||||
when(mockResponse.getBody()).thenReturn(body);
|
||||
|
||||
List<HttpMessageReader<?>> messageReaders = Collections
|
||||
.singletonList(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()));
|
||||
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders);
|
||||
|
||||
ResponseEntity<List<String>> result = defaultClientResponse.toEntityList(String.class).block();
|
||||
assertEquals(Collections.singletonList("foo"), result.getBody());
|
||||
try {
|
||||
result.getStatusCode();
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// do nothing
|
||||
}
|
||||
assertEquals(999, result.getStatusCodeValue());
|
||||
assertEquals(MediaType.TEXT_PLAIN, result.getHeaders().getContentType());
|
||||
}
|
||||
|
||||
|
|
@ -304,6 +383,7 @@ public class DefaultClientResponseTests {
|
|||
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
|
||||
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
|
||||
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
|
||||
when(mockResponse.getBody()).thenReturn(body);
|
||||
|
||||
List<HttpMessageReader<?>> messageReaders = Collections
|
||||
|
|
@ -315,6 +395,7 @@ public class DefaultClientResponseTests {
|
|||
}).block();
|
||||
assertEquals(Collections.singletonList("foo"), result.getBody());
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals(HttpStatus.OK.value(), result.getStatusCodeValue());
|
||||
assertEquals(MediaType.TEXT_PLAIN, result.getHeaders().getContentType());
|
||||
}
|
||||
|
||||
|
|
@ -326,6 +407,7 @@ public class DefaultClientResponseTests {
|
|||
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
|
||||
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
|
||||
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
|
||||
when(mockResponse.getBody()).thenReturn(body.flux());
|
||||
|
||||
List<HttpMessageReader<?>> messageReaders = Collections
|
||||
|
|
@ -351,6 +433,7 @@ public class DefaultClientResponseTests {
|
|||
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
|
||||
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
|
||||
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
|
||||
when(mockResponse.getBody()).thenReturn(body.flux());
|
||||
|
||||
List<HttpMessageReader<?>> messageReaders = Collections
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ import static org.junit.Assert.*;
|
|||
*
|
||||
* @author Brian Clozel
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Denys Ivano
|
||||
*/
|
||||
public class WebClientIntegrationTests {
|
||||
|
||||
|
|
@ -413,7 +414,7 @@ public class WebClientIntegrationTests {
|
|||
.bodyToMono(String.class);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectError(WebClientException.class)
|
||||
.expectError(WebClientResponseException.class)
|
||||
.verify(Duration.ofSeconds(3));
|
||||
|
||||
expectRequestCount(1);
|
||||
|
|
@ -433,7 +434,7 @@ public class WebClientIntegrationTests {
|
|||
.bodyToMono(String.class);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectError(WebClientException.class)
|
||||
.expectError(WebClientResponseException.class)
|
||||
.verify(Duration.ofSeconds(3));
|
||||
|
||||
expectRequestCount(1);
|
||||
|
|
@ -459,6 +460,9 @@ public class WebClientIntegrationTests {
|
|||
assertTrue(throwable instanceof WebClientResponseException);
|
||||
WebClientResponseException ex = (WebClientResponseException) throwable;
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, ex.getStatusCode());
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getRawStatusCode());
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(),
|
||||
ex.getStatusText());
|
||||
assertEquals(MediaType.TEXT_PLAIN, ex.getHeaders().getContentType());
|
||||
assertEquals(errorMessage, ex.getResponseBodyAsString());
|
||||
})
|
||||
|
|
@ -471,6 +475,62 @@ public class WebClientIntegrationTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSupportUnknownStatusCode() {
|
||||
int errorStatus = 555;
|
||||
assertNull(HttpStatus.resolve(errorStatus));
|
||||
String errorMessage = "Something went wrong";
|
||||
prepareResponse(response -> response.setResponseCode(errorStatus)
|
||||
.setHeader("Content-Type", "text/plain").setBody(errorMessage));
|
||||
|
||||
Mono<ClientResponse> result = this.webClient.get()
|
||||
.uri("/unknownPage")
|
||||
.exchange();
|
||||
|
||||
StepVerifier.create(result)
|
||||
.consumeNextWith(response -> assertEquals(555, response.rawStatusCode()))
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3));
|
||||
|
||||
expectRequestCount(1);
|
||||
expectRequest(request -> {
|
||||
assertEquals("*/*", request.getHeader(HttpHeaders.ACCEPT));
|
||||
assertEquals("/unknownPage", request.getPath());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetErrorSignalWhenRetrievingUnknownStatusCode() {
|
||||
int errorStatus = 555;
|
||||
assertNull(HttpStatus.resolve(errorStatus));
|
||||
String errorMessage = "Something went wrong";
|
||||
prepareResponse(response -> response.setResponseCode(errorStatus)
|
||||
.setHeader("Content-Type", "text/plain").setBody(errorMessage));
|
||||
|
||||
Mono<String> result = this.webClient.get()
|
||||
.uri("/unknownPage")
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectErrorSatisfies(throwable -> {
|
||||
assertTrue(throwable instanceof UnknownHttpStatusCodeException);
|
||||
UnknownHttpStatusCodeException ex = (UnknownHttpStatusCodeException) throwable;
|
||||
assertEquals("Unknown status code ["+errorStatus+"]", ex.getMessage());
|
||||
assertEquals(errorStatus, ex.getRawStatusCode());
|
||||
assertEquals("", ex.getStatusText());
|
||||
assertEquals(MediaType.TEXT_PLAIN, ex.getHeaders().getContentType());
|
||||
assertEquals(errorMessage, ex.getResponseBodyAsString());
|
||||
})
|
||||
.verify(Duration.ofSeconds(3));
|
||||
|
||||
expectRequestCount(1);
|
||||
expectRequest(request -> {
|
||||
assertEquals("*/*", request.getHeader(HttpHeaders.ACCEPT));
|
||||
assertEquals("/unknownPage", request.getPath());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldApplyCustomStatusHandler() {
|
||||
prepareResponse(response -> response.setResponseCode(500)
|
||||
|
|
|
|||
|
|
@ -65,6 +65,14 @@ public class ClientResponseWrapperTests {
|
|||
assertSame(status, wrapper.statusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rawStatusCode() {
|
||||
int status = 999;
|
||||
when(mockResponse.rawStatusCode()).thenReturn(status);
|
||||
|
||||
assertEquals(status, wrapper.rawStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headers() {
|
||||
ClientResponse.Headers headers = mock(ClientResponse.Headers.class);
|
||||
|
|
|
|||
Loading…
Reference in New Issue