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 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.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* 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);
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue