Support for non-standard HTTP status in reactive ClientHttpResponse

Issue: SPR-16748
This commit is contained in:
Juergen Hoeller
2018-04-27 18:25:11 +02:00
parent 44cf002c00
commit a683472daa
12 changed files with 92 additions and 75 deletions

View File

@@ -86,7 +86,6 @@ public interface ClientRequest {
}
}
/**
* Return the attributes of this request.
*/
@@ -222,8 +221,7 @@ public interface ClientRequest {
* @param <P> the type of the {@code Publisher}
* @return the built request
*/
<S, P extends Publisher<S>> Builder body(P publisher,
ParameterizedTypeReference<S> typeReference);
<S, P extends Publisher<S>> Builder body(P publisher, ParameterizedTypeReference<S> typeReference);
/**
* Set the attribute with the given name to the given value.
@@ -243,8 +241,7 @@ public interface ClientRequest {
Builder attributes(Consumer<Map<String, Object>> attributesConsumer);
/**
* Builds the request.
* @return the request
* Build the request.
*/
ClientRequest build();
}

View File

@@ -160,13 +160,13 @@ public interface ClientResponse {
* @return the created builder
*/
static Builder from(ClientResponse other) {
Assert.notNull(other, "'other' must not be null");
Assert.notNull(other, "Other ClientResponse must not be null");
return new DefaultClientResponseBuilder(other);
}
/**
* Create a response builder with the given status code and using default strategies for reading
* the body.
* Create a response builder with the given status code and using default strategies for
* reading the body.
* @param statusCode the status code
* @return the created builder
*/
@@ -181,10 +181,7 @@ public interface ClientResponse {
* @return the created builder
*/
static Builder create(HttpStatus statusCode, ExchangeStrategies strategies) {
Assert.notNull(statusCode, "'statusCode' must not be null");
Assert.notNull(strategies, "'strategies' must not be null");
return new DefaultClientResponseBuilder(strategies)
.statusCode(statusCode);
return new DefaultClientResponseBuilder(strategies).statusCode(statusCode);
}
/**
@@ -194,24 +191,20 @@ public interface ClientResponse {
* @return the created builder
*/
static Builder create(HttpStatus statusCode, List<HttpMessageReader<?>> messageReaders) {
Assert.notNull(statusCode, "'statusCode' must not be null");
Assert.notNull(messageReaders, "'messageReaders' must not be null");
return create(statusCode, new ExchangeStrategies() {
@Override
public List<HttpMessageReader<?>> messageReaders() {
return messageReaders;
}
@Override
public List<HttpMessageWriter<?>> messageWriters() {
// not used in the response
return Collections.emptyList();
}
});
}
/**
* Represents the headers of the HTTP response.
* @see ClientResponse#headers()
@@ -243,6 +236,7 @@ public interface ClientResponse {
HttpHeaders asHttpHeaders();
}
/**
* Defines a builder for a response.
*/
@@ -295,7 +289,7 @@ public interface ClientResponse {
Builder cookies(Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer);
/**
* Sets the body of the response. Calling this methods will
* Set the body of the response. Calling this methods will
* {@linkplain org.springframework.core.io.buffer.DataBufferUtils#release(DataBuffer) release}
* the existing body of the builder.
* @param body the new body.
@@ -304,7 +298,7 @@ public interface ClientResponse {
Builder body(Flux<DataBuffer> body);
/**
* Sets the body of the response to the UTF-8 encoded bytes of the given string.
* Set the body of the response to the UTF-8 encoded bytes of the given string.
* Calling this methods will
* {@linkplain org.springframework.core.io.buffer.DataBufferUtils#release(DataBuffer) release}
* the existing body of the builder.
@@ -314,9 +308,9 @@ public interface ClientResponse {
Builder body(String body);
/**
* Builds the response.
* @return the response
* Build the response.
*/
ClientResponse build();
}
}

View File

@@ -61,6 +61,7 @@ class DefaultClientResponse implements ClientResponse {
this.headers = new DefaultHeaders();
}
@Override
public ExchangeStrategies strategies() {
return this.strategies;
@@ -88,12 +89,10 @@ class DefaultClientResponse implements ClientResponse {
public List<HttpMessageReader<?>> messageReaders() {
return strategies.messageReaders();
}
@Override
public Optional<ServerHttpResponse> serverResponse() {
return Optional.empty();
}
@Override
public Map<String, Object> hints() {
return Collections.emptyMap();
@@ -187,8 +186,7 @@ class DefaultClientResponse implements ClientResponse {
}
@Override
public <T> Mono<ResponseEntity<List<T>>> toEntityList(
ParameterizedTypeReference<T> typeReference) {
public <T> Mono<ResponseEntity<List<T>>> toEntityList(ParameterizedTypeReference<T> typeReference) {
return toEntityListInternal(bodyToFlux(typeReference));
}
@@ -220,7 +218,7 @@ class DefaultClientResponse implements ClientResponse {
@Override
public List<String> header(String headerName) {
List<String> headerValues = delegate().get(headerName);
return headerValues != null ? headerValues : Collections.emptyList();
return (headerValues != null ? headerValues : Collections.emptyList());
}
@Override
@@ -229,12 +227,13 @@ class DefaultClientResponse implements ClientResponse {
}
private OptionalLong toOptionalLong(long value) {
return value != -1 ? OptionalLong.of(value) : OptionalLong.empty();
return (value != -1 ? OptionalLong.of(value) : OptionalLong.empty());
}
}
@SuppressWarnings("serial")
private class ReadCancellationException extends RuntimeException {
private static class ReadCancellationException extends RuntimeException {
}
}

View File

@@ -29,7 +29,6 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
@@ -55,7 +54,7 @@ class DefaultClientResponseBuilder implements ClientResponse.Builder {
public DefaultClientResponseBuilder(ExchangeStrategies strategies) {
Assert.notNull(strategies, "'strategies' must not be null");
Assert.notNull(strategies, "ExchangeStrategies must not be null");
this.strategies = strategies;
}
@@ -66,9 +65,10 @@ class DefaultClientResponseBuilder implements ClientResponse.Builder {
cookies(cookies -> cookies.addAll(other.cookies()));
}
@Override
public DefaultClientResponseBuilder statusCode(HttpStatus statusCode) {
Assert.notNull(statusCode, "'statusCode' must not be null");
Assert.notNull(statusCode, "HttpStatus must not be null");
this.statusCode = statusCode;
return this;
}
@@ -83,7 +83,7 @@ class DefaultClientResponseBuilder implements ClientResponse.Builder {
@Override
public ClientResponse.Builder headers(Consumer<HttpHeaders> headersConsumer) {
Assert.notNull(headersConsumer, "'headersConsumer' must not be null");
Assert.notNull(headersConsumer, "Consumer must not be null");
headersConsumer.accept(this.headers);
return this;
}
@@ -97,16 +97,15 @@ class DefaultClientResponseBuilder implements ClientResponse.Builder {
}
@Override
public ClientResponse.Builder cookies(
Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer) {
Assert.notNull(cookiesConsumer, "'cookiesConsumer' must not be null");
public ClientResponse.Builder cookies(Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer) {
Assert.notNull(cookiesConsumer, "Consumer must not be null");
cookiesConsumer.accept(this.cookies);
return this;
}
@Override
public ClientResponse.Builder body(Flux<DataBuffer> body) {
Assert.notNull(body, "'body' must not be null");
Assert.notNull(body, "Body must not be null");
releaseBody();
this.body = body;
return this;
@@ -114,7 +113,7 @@ class DefaultClientResponseBuilder implements ClientResponse.Builder {
@Override
public ClientResponse.Builder body(String body) {
Assert.notNull(body, "'body' must not be null");
Assert.notNull(body, "Body must not be null");
releaseBody();
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
this.body = Flux.just(body).
@@ -131,11 +130,12 @@ class DefaultClientResponseBuilder implements ClientResponse.Builder {
@Override
public ClientResponse build() {
ClientHttpResponse clientHttpResponse = new BuiltClientHttpResponse(this.statusCode,
this.headers, this.cookies, this.body);
ClientHttpResponse clientHttpResponse = new BuiltClientHttpResponse(
this.statusCode, this.headers, this.cookies, this.body);
return new DefaultClientResponse(clientHttpResponse, this.strategies);
}
private static class BuiltClientHttpResponse implements ClientHttpResponse {
private final HttpStatus statusCode;
@@ -147,29 +147,24 @@ class DefaultClientResponseBuilder implements ClientResponse.Builder {
private final Flux<DataBuffer> body;
public BuiltClientHttpResponse(HttpStatus statusCode, HttpHeaders headers,
MultiValueMap<String, ResponseCookie> cookies,
Flux<DataBuffer> body) {
MultiValueMap<String, ResponseCookie> cookies, Flux<DataBuffer> body) {
this.statusCode = statusCode;
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
this.cookies = unmodifiableCopy(cookies);
this.cookies = CollectionUtils.unmodifiableMultiValueMap(cookies);
this.body = body;
}
private static @Nullable <K, V> MultiValueMap<K, V> unmodifiableCopy(@Nullable MultiValueMap<K, V> original) {
if (original != null) {
return CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<>(original));
}
else {
return null;
}
}
@Override
public HttpStatus getStatusCode() {
return this.statusCode;
}
@Override
public int getRawStatusCode() {
return this.statusCode.value();
}
@Override
public HttpHeaders getHeaders() {
return this.headers;

View File

@@ -55,8 +55,8 @@ public abstract class ExchangeFunctions {
* @return the created function
*/
public static ExchangeFunction create(ClientHttpConnector connector, ExchangeStrategies strategies) {
Assert.notNull(connector, "'connector' must not be null");
Assert.notNull(strategies, "'strategies' must not be null");
Assert.notNull(connector, "ClientHttpConnector must not be null");
Assert.notNull(strategies, "ExchangeStrategies must not be null");
return new DefaultExchangeFunction(connector, strategies);
}
@@ -74,7 +74,7 @@ public abstract class ExchangeFunctions {
@Override
public Mono<ClientResponse> exchange(ClientRequest request) {
Assert.notNull(request, "'request' must not be null");
Assert.notNull(request, "ClientRequest must not be null");
return this.connector
.connect(request.method(), request.url(),
clientHttpRequest -> request.writeTo(clientHttpRequest, this.strategies))
@@ -82,9 +82,11 @@ public abstract class ExchangeFunctions {
.doOnRequest(n -> logger.debug("Demand signaled"))
.doOnCancel(() -> logger.debug("Cancelling request"))
.map(response -> {
HttpStatus status = response.getStatusCode();
if (logger.isDebugEnabled()) {
logger.debug("Response received, status: " + status + " " + status.getReasonPhrase());
int status = response.getRawStatusCode();
HttpStatus resolvedStatus = HttpStatus.resolve(status);
logger.debug("Response received, status: " + status +
(resolvedStatus != null ? " " + resolvedStatus.getReasonPhrase() : ""));
}
return new DefaultClientResponse(response, this.strategies);
});

View File

@@ -280,12 +280,11 @@ public interface ServerRequest {
* @param messageReaders the message readers
* @return the created {@code ServerRequest}
*/
static ServerRequest create(ServerWebExchange exchange,
List<HttpMessageReader<?>> messageReaders) {
static ServerRequest create(ServerWebExchange exchange, List<HttpMessageReader<?>> messageReaders) {
return new DefaultServerRequest(exchange, messageReaders);
}
/**
* Represents the headers of the HTTP request.
* @see ServerRequest#headers()

View File

@@ -322,7 +322,6 @@ public interface ServerResponse {
/**
* Build the response entity with no body.
* @return the built response
*/
Mono<ServerResponse> build();
@@ -330,14 +329,12 @@ public interface ServerResponse {
* Build the response entity with no body.
* The response will be committed when the given {@code voidPublisher} completes.
* @param voidPublisher publisher publisher to indicate when the response should be committed
* @return the built response
*/
Mono<ServerResponse> build(Publisher<Void> voidPublisher);
/**
* Build the response entity with a custom writer function.
* @param writeFunction the function used to write to the {@link ServerWebExchange}
* @return the built response
*/
Mono<ServerResponse> build(BiFunction<ServerWebExchange, Context, Mono<Void>> writeFunction);
}