Support for non-standard HTTP status in reactive ClientHttpResponse
Issue: SPR-16748
(cherry picked from commit a683472)
This commit is contained in:
parent
f2e77c292d
commit
a9548f93e4
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 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.
|
||||
|
|
@ -40,6 +40,7 @@ import org.springframework.util.MultiValueMap;
|
|||
|
||||
/**
|
||||
* Mock implementation of {@link ClientHttpResponse}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
|
|
@ -68,6 +69,11 @@ public class MockClientHttpResponse implements ClientHttpResponse {
|
|||
return this.status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawStatusCode() {
|
||||
return this.status.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
String headerName = HttpHeaders.SET_COOKIE;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -31,10 +31,23 @@ import org.springframework.util.MultiValueMap;
|
|||
public interface ClientHttpResponse extends ReactiveHttpInputMessage {
|
||||
|
||||
/**
|
||||
* Return the HTTP status as an {@link HttpStatus} enum value.
|
||||
* Return the HTTP status code of the response.
|
||||
* @return the HTTP status as an HttpStatus enum value
|
||||
* @throws IllegalArgumentException in case of an unknown HTTP status code
|
||||
* @see HttpStatus#valueOf(int)
|
||||
*/
|
||||
HttpStatus getStatusCode();
|
||||
|
||||
/**
|
||||
* Return the HTTP status code (potentially non-standard and not
|
||||
* resolvable through the {@link HttpStatus} enum) as an integer.
|
||||
* @return the HTTP status as an integer
|
||||
* @since 5.0.6
|
||||
* @see #getStatusCode()
|
||||
* @see HttpStatus#resolve(int)
|
||||
*/
|
||||
int getRawStatusCode();
|
||||
|
||||
/**
|
||||
* Return a read-only map of response cookies received from the server.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -55,6 +55,11 @@ public class ClientHttpResponseDecorator implements ClientHttpResponse {
|
|||
return this.delegate.getStatusCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawStatusCode() {
|
||||
return this.delegate.getRawStatusCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return this.delegate.getHeaders();
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -62,14 +62,18 @@ class ReactorClientHttpResponse implements ClientHttpResponse {
|
|||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
this.response.responseHeaders().entries()
|
||||
.forEach(e -> headers.add(e.getKey(), e.getValue()));
|
||||
this.response.responseHeaders().entries().forEach(e -> headers.add(e.getKey(), e.getValue()));
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus getStatusCode() {
|
||||
return HttpStatus.valueOf(this.response.status().code());
|
||||
return HttpStatus.valueOf(getRawStatusCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawStatusCode() {
|
||||
return this.response.status().code();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -91,7 +95,7 @@ class ReactorClientHttpResponse implements ClientHttpResponse {
|
|||
public String toString() {
|
||||
return "ReactorClientHttpResponse{" +
|
||||
"request=[" + this.response.method().name() + " " + this.response.uri() + "]," +
|
||||
"status=" + getStatusCode() + '}';
|
||||
"status=" + getRawStatusCode() + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 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.
|
||||
|
|
@ -40,6 +40,7 @@ import org.springframework.util.MultiValueMap;
|
|||
|
||||
/**
|
||||
* Mock implementation of {@link ClientHttpResponse}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
|
|
@ -68,6 +69,11 @@ public class MockClientHttpResponse implements ClientHttpResponse {
|
|||
return this.status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawStatusCode() {
|
||||
return this.status.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
String headerName = HttpHeaders.SET_COOKIE;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -274,12 +274,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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue