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:
Brian Clozel 2018-07-12 19:08:08 +02:00
parent f123d6c3bc
commit 038af9a303
10 changed files with 253 additions and 28 deletions

View File

@ -60,9 +60,21 @@ public interface ClientResponse {
/** /**
* Return the status code of this response. * 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(); 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. * Return the headers of this response.
*/ */

View File

@ -76,6 +76,11 @@ class DefaultClientResponse implements ClientResponse {
return this.response.getStatusCode(); return this.response.getStatusCode();
} }
@Override
public int rawStatusCode() {
return this.response.getRawStatusCode();
}
@Override @Override
public Headers headers() { public Headers headers() {
return this.headers; return this.headers;
@ -177,11 +182,11 @@ class DefaultClientResponse implements ClientResponse {
private <T> Mono<ResponseEntity<T>> toEntityInternal(Mono<T> bodyMono) { private <T> Mono<ResponseEntity<T>> toEntityInternal(Mono<T> bodyMono) {
HttpHeaders headers = headers().asHttpHeaders(); HttpHeaders headers = headers().asHttpHeaders();
HttpStatus statusCode = statusCode(); int status = rawStatusCode();
return bodyMono return bodyMono
.map(body -> new ResponseEntity<>(body, headers, statusCode)) .map(body -> createEntity(body, headers, status))
.switchIfEmpty(Mono.defer( .switchIfEmpty(Mono.defer(
() -> Mono.just(new ResponseEntity<>(headers, statusCode)))); () -> Mono.just(createEntity(headers, status))));
} }
@Override @Override
@ -196,10 +201,24 @@ class DefaultClientResponse implements ClientResponse {
private <T> Mono<ResponseEntity<List<T>>> toEntityListInternal(Flux<T> bodyFlux) { private <T> Mono<ResponseEntity<List<T>>> toEntityListInternal(Flux<T> bodyFlux) {
HttpHeaders headers = headers().asHttpHeaders(); HttpHeaders headers = headers().asHttpHeaders();
HttpStatus statusCode = statusCode(); int status = rawStatusCode();
return bodyFlux return bodyFlux
.collectList() .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);
} }

View File

@ -59,6 +59,7 @@ import org.springframework.web.util.UriBuilderFactory;
* Default implementation of {@link WebClient}. * Default implementation of {@link WebClient}.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Brian Clozel
* @since 5.0 * @since 5.0
*/ */
class DefaultWebClient implements WebClient { class DefaultWebClient implements WebClient {
@ -375,7 +376,7 @@ class DefaultWebClient implements WebClient {
private final Mono<ClientResponse> responseMono; private final Mono<ClientResponse> responseMono;
private List<StatusHandler> statusHandlers = new ArrayList<>(1); private final List<StatusHandler> statusHandlers = new ArrayList<>(1);
DefaultResponseSpec(Mono<ClientResponse> responseMono) { DefaultResponseSpec(Mono<ClientResponse> responseMono) {
this.responseMono = responseMono; this.responseMono = responseMono;
@ -435,13 +436,17 @@ class DefaultWebClient implements WebClient {
private <T extends Publisher<?>> T bodyToPublisher(ClientResponse response, private <T extends Publisher<?>> T bodyToPublisher(ClientResponse response,
T bodyPublisher, Function<Mono<? extends Throwable>, T> errorFunction) { T bodyPublisher, Function<Mono<? extends Throwable>, T> errorFunction) {
if (HttpStatus.resolve(response.rawStatusCode()) != null) {
return this.statusHandlers.stream() return this.statusHandlers.stream()
.filter(statusHandler -> statusHandler.test(response.statusCode())) .filter(statusHandler -> statusHandler.test(response.statusCode()))
.findFirst() .findFirst()
.map(statusHandler -> statusHandler.apply(response)) .map(statusHandler -> statusHandler.apply(response))
.map(errorFunction::apply) .map(errorFunction::apply)
.orElse(bodyPublisher); .orElse(bodyPublisher);
}
else {
return errorFunction.apply(createResponseException(response));
}
} }
private static Mono<WebClientResponseException> createResponseException(ClientResponse response) { private static Mono<WebClientResponseException> createResponseException(ClientResponse response) {
@ -454,18 +459,26 @@ class DefaultWebClient implements WebClient {
}) })
.defaultIfEmpty(new byte[0]) .defaultIfEmpty(new byte[0])
.map(bodyBytes -> { .map(bodyBytes -> {
String msg = String.format("ClientResponse has erroneous status code: %d %s", response.statusCode().value(),
response.statusCode().getReasonPhrase());
Charset charset = response.headers().contentType() Charset charset = response.headers().contentType()
.map(MimeType::getCharset) .map(MimeType::getCharset)
.orElse(StandardCharsets.ISO_8859_1); .orElse(StandardCharsets.ISO_8859_1);
return new WebClientResponseException(msg, if (HttpStatus.resolve(response.rawStatusCode()) != null) {
response.statusCode().value(), String msg = String.format("ClientResponse has erroneous status code: %d %s",
response.statusCode().getReasonPhrase(), response.statusCode().value(), response.statusCode().getReasonPhrase());
response.headers().asHttpHeaders(), return new WebClientResponseException(msg,
bodyBytes, response.statusCode().value(),
charset response.statusCode().getReasonPhrase(),
); response.headers().asHttpHeaders(),
bodyBytes,
charset);
}
else {
return new UnknownHttpStatusCodeException(
response.rawStatusCode(),
response.headers().asHttpHeaders(),
bodyBytes,
charset);
}
}); });
} }

View File

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

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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. * Construct a new instance of with the given response data.
* @param message the exception message
* @param statusCode the raw status code value * @param statusCode the raw status code value
* @param statusText the status text * @param statusText the status text
* @param headers the response headers (may be {@code null}) * @param headers the response headers (may be {@code null})
@ -69,6 +70,7 @@ public class WebClientResponseException extends WebClientException {
/** /**
* Return the HTTP status code value. * Return the HTTP status code value.
* @throws IllegalArgumentException in case of an unknown HTTP status code
*/ */
public HttpStatus getStatusCode() { public HttpStatus getStatusCode() {
return HttpStatus.valueOf(this.statusCode); return HttpStatus.valueOf(this.statusCode);

View File

@ -77,6 +77,11 @@ public class ClientResponseWrapper implements ClientResponse {
return this.delegate.statusCode(); return this.delegate.statusCode();
} }
@Override
public int rawStatusCode() {
return this.delegate.rawStatusCode();
}
@Override @Override
public Headers headers() { public Headers headers() {
return this.delegate.headers(); return this.delegate.headers();

View File

@ -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 * 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 * @param requestProcessor the request processor
* @return the filter adaptation of 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 * 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 * @param responseProcessor the response processor
* @return the filter adaptation of the request processor * @return the filter adaptation of the request processor
*/ */

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 Arjen Poutsma
* @author Denys Ivano
*/ */
public class DefaultClientResponseTests { public class DefaultClientResponseTests {
@ -80,6 +81,14 @@ public class DefaultClientResponseTests {
assertEquals(status, defaultClientResponse.statusCode()); assertEquals(status, defaultClientResponse.statusCode());
} }
@Test
public void rawStatusCode() {
int status = 999;
when(mockResponse.getRawStatusCode()).thenReturn(status);
assertEquals(status, defaultClientResponse.rawStatusCode());
}
@Test @Test
public void header() { public void header() {
HttpHeaders httpHeaders = new HttpHeaders(); HttpHeaders httpHeaders = new HttpHeaders();
@ -143,6 +152,7 @@ public class DefaultClientResponseTests {
httpHeaders.setContentType(MediaType.TEXT_PLAIN); httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders); when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK); when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
when(mockResponse.getBody()).thenReturn(body); when(mockResponse.getBody()).thenReturn(body);
List<HttpMessageReader<?>> messageReaders = Collections List<HttpMessageReader<?>> messageReaders = Collections
@ -164,6 +174,7 @@ public class DefaultClientResponseTests {
httpHeaders.setContentType(MediaType.TEXT_PLAIN); httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders); when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK); when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
when(mockResponse.getBody()).thenReturn(body); when(mockResponse.getBody()).thenReturn(body);
List<HttpMessageReader<?>> messageReaders = Collections List<HttpMessageReader<?>> messageReaders = Collections
@ -187,6 +198,7 @@ public class DefaultClientResponseTests {
httpHeaders.setContentType(MediaType.TEXT_PLAIN); httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders); when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK); when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
when(mockResponse.getBody()).thenReturn(body); when(mockResponse.getBody()).thenReturn(body);
List<HttpMessageReader<?>> messageReaders = Collections List<HttpMessageReader<?>> messageReaders = Collections
@ -209,6 +221,7 @@ public class DefaultClientResponseTests {
httpHeaders.setContentType(MediaType.TEXT_PLAIN); httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders); when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK); when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
when(mockResponse.getBody()).thenReturn(body); when(mockResponse.getBody()).thenReturn(body);
List<HttpMessageReader<?>> messageReaders = Collections List<HttpMessageReader<?>> messageReaders = Collections
@ -233,6 +246,7 @@ public class DefaultClientResponseTests {
httpHeaders.setContentType(MediaType.TEXT_PLAIN); httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders); when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK); when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
when(mockResponse.getBody()).thenReturn(body); when(mockResponse.getBody()).thenReturn(body);
List<HttpMessageReader<?>> messageReaders = Collections List<HttpMessageReader<?>> messageReaders = Collections
@ -242,6 +256,37 @@ public class DefaultClientResponseTests {
ResponseEntity<String> result = defaultClientResponse.toEntity(String.class).block(); ResponseEntity<String> result = defaultClientResponse.toEntity(String.class).block();
assertEquals("foo", result.getBody()); assertEquals("foo", result.getBody());
assertEquals(HttpStatus.OK, result.getStatusCode()); 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()); assertEquals(MediaType.TEXT_PLAIN, result.getHeaders().getContentType());
} }
@ -256,6 +301,7 @@ public class DefaultClientResponseTests {
httpHeaders.setContentType(MediaType.TEXT_PLAIN); httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders); when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK); when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
when(mockResponse.getBody()).thenReturn(body); when(mockResponse.getBody()).thenReturn(body);
List<HttpMessageReader<?>> messageReaders = Collections List<HttpMessageReader<?>> messageReaders = Collections
@ -267,6 +313,7 @@ public class DefaultClientResponseTests {
}).block(); }).block();
assertEquals("foo", result.getBody()); assertEquals("foo", result.getBody());
assertEquals(HttpStatus.OK, result.getStatusCode()); assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(HttpStatus.OK.value(), result.getStatusCodeValue());
assertEquals(MediaType.TEXT_PLAIN, result.getHeaders().getContentType()); assertEquals(MediaType.TEXT_PLAIN, result.getHeaders().getContentType());
} }
@ -281,6 +328,7 @@ public class DefaultClientResponseTests {
httpHeaders.setContentType(MediaType.TEXT_PLAIN); httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders); when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK); when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
when(mockResponse.getBody()).thenReturn(body); when(mockResponse.getBody()).thenReturn(body);
List<HttpMessageReader<?>> messageReaders = Collections List<HttpMessageReader<?>> messageReaders = Collections
@ -290,6 +338,37 @@ public class DefaultClientResponseTests {
ResponseEntity<List<String>> result = defaultClientResponse.toEntityList(String.class).block(); ResponseEntity<List<String>> result = defaultClientResponse.toEntityList(String.class).block();
assertEquals(Collections.singletonList("foo"), result.getBody()); assertEquals(Collections.singletonList("foo"), result.getBody());
assertEquals(HttpStatus.OK, result.getStatusCode()); 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()); assertEquals(MediaType.TEXT_PLAIN, result.getHeaders().getContentType());
} }
@ -304,6 +383,7 @@ public class DefaultClientResponseTests {
httpHeaders.setContentType(MediaType.TEXT_PLAIN); httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders); when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK); when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
when(mockResponse.getBody()).thenReturn(body); when(mockResponse.getBody()).thenReturn(body);
List<HttpMessageReader<?>> messageReaders = Collections List<HttpMessageReader<?>> messageReaders = Collections
@ -315,6 +395,7 @@ public class DefaultClientResponseTests {
}).block(); }).block();
assertEquals(Collections.singletonList("foo"), result.getBody()); assertEquals(Collections.singletonList("foo"), result.getBody());
assertEquals(HttpStatus.OK, result.getStatusCode()); assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(HttpStatus.OK.value(), result.getStatusCodeValue());
assertEquals(MediaType.TEXT_PLAIN, result.getHeaders().getContentType()); assertEquals(MediaType.TEXT_PLAIN, result.getHeaders().getContentType());
} }
@ -326,6 +407,7 @@ public class DefaultClientResponseTests {
httpHeaders.setContentType(MediaType.TEXT_PLAIN); httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders); when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK); when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
when(mockResponse.getBody()).thenReturn(body.flux()); when(mockResponse.getBody()).thenReturn(body.flux());
List<HttpMessageReader<?>> messageReaders = Collections List<HttpMessageReader<?>> messageReaders = Collections
@ -351,6 +433,7 @@ public class DefaultClientResponseTests {
httpHeaders.setContentType(MediaType.TEXT_PLAIN); httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders); when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK); when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
when(mockResponse.getRawStatusCode()).thenReturn(HttpStatus.OK.value());
when(mockResponse.getBody()).thenReturn(body.flux()); when(mockResponse.getBody()).thenReturn(body.flux());
List<HttpMessageReader<?>> messageReaders = Collections List<HttpMessageReader<?>> messageReaders = Collections

View File

@ -55,6 +55,7 @@ import static org.junit.Assert.*;
* *
* @author Brian Clozel * @author Brian Clozel
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Denys Ivano
*/ */
public class WebClientIntegrationTests { public class WebClientIntegrationTests {
@ -413,7 +414,7 @@ public class WebClientIntegrationTests {
.bodyToMono(String.class); .bodyToMono(String.class);
StepVerifier.create(result) StepVerifier.create(result)
.expectError(WebClientException.class) .expectError(WebClientResponseException.class)
.verify(Duration.ofSeconds(3)); .verify(Duration.ofSeconds(3));
expectRequestCount(1); expectRequestCount(1);
@ -433,7 +434,7 @@ public class WebClientIntegrationTests {
.bodyToMono(String.class); .bodyToMono(String.class);
StepVerifier.create(result) StepVerifier.create(result)
.expectError(WebClientException.class) .expectError(WebClientResponseException.class)
.verify(Duration.ofSeconds(3)); .verify(Duration.ofSeconds(3));
expectRequestCount(1); expectRequestCount(1);
@ -459,6 +460,9 @@ public class WebClientIntegrationTests {
assertTrue(throwable instanceof WebClientResponseException); assertTrue(throwable instanceof WebClientResponseException);
WebClientResponseException ex = (WebClientResponseException) throwable; WebClientResponseException ex = (WebClientResponseException) throwable;
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, ex.getStatusCode()); 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(MediaType.TEXT_PLAIN, ex.getHeaders().getContentType());
assertEquals(errorMessage, ex.getResponseBodyAsString()); 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 @Test
public void shouldApplyCustomStatusHandler() { public void shouldApplyCustomStatusHandler() {
prepareResponse(response -> response.setResponseCode(500) prepareResponse(response -> response.setResponseCode(500)

View File

@ -65,6 +65,14 @@ public class ClientResponseWrapperTests {
assertSame(status, wrapper.statusCode()); assertSame(status, wrapper.statusCode());
} }
@Test
public void rawStatusCode() {
int status = 999;
when(mockResponse.rawStatusCode()).thenReturn(status);
assertEquals(status, wrapper.rawStatusCode());
}
@Test @Test
public void headers() { public void headers() {
ClientResponse.Headers headers = mock(ClientResponse.Headers.class); ClientResponse.Headers headers = mock(ClientResponse.Headers.class);