Browse Source

Support for non-standard HTTP status in reactive ClientHttpResponse

Issue: SPR-16748
pull/1811/merge
Juergen Hoeller 8 years ago
parent
commit
a683472daa
  1. 8
      spring-test/src/main/java/org/springframework/mock/http/client/reactive/MockClientHttpResponse.java
  2. 17
      spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpResponse.java
  3. 7
      spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpResponseDecorator.java
  4. 14
      spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java
  5. 8
      spring-web/src/test/java/org/springframework/mock/http/client/reactive/test/MockClientHttpResponse.java
  6. 7
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequest.java
  7. 26
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientResponse.java
  8. 15
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java
  9. 41
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java
  10. 12
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java
  11. 5
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java
  12. 3
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java

8
spring-test/src/main/java/org/springframework/mock/http/client/reactive/MockClientHttpResponse.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 { @@ -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;

17
spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpResponse.java

@ -1,5 +1,5 @@ @@ -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; @@ -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.
*/

7
spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpResponseDecorator.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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();

14
spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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 { @@ -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() + '}';
}
}

8
spring-web/src/test/java/org/springframework/mock/http/client/reactive/test/MockClientHttpResponse.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 { @@ -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;

7
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequest.java

@ -86,7 +86,6 @@ public interface ClientRequest { @@ -86,7 +86,6 @@ public interface ClientRequest {
}
}
/**
* Return the attributes of this request.
*/
@ -222,8 +221,7 @@ public interface ClientRequest { @@ -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 { @@ -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();
}

26
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientResponse.java

@ -160,13 +160,13 @@ public interface ClientResponse { @@ -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 { @@ -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 { @@ -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 { @@ -243,6 +236,7 @@ public interface ClientResponse {
HttpHeaders asHttpHeaders();
}
/**
* Defines a builder for a response.
*/
@ -295,7 +289,7 @@ public interface ClientResponse { @@ -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 { @@ -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 { @@ -314,9 +308,9 @@ public interface ClientResponse {
Builder body(String body);
/**
* Builds the response.
* @return the response
* Build the response.
*/
ClientResponse build();
}
}

15
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java

@ -61,6 +61,7 @@ class DefaultClientResponse implements ClientResponse { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 {
}
}

41
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java

@ -29,7 +29,6 @@ import org.springframework.http.HttpHeaders; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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;

12
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java

@ -55,8 +55,8 @@ public abstract class ExchangeFunctions { @@ -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 { @@ -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 { @@ -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);
});

5
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java

@ -280,12 +280,11 @@ public interface ServerRequest { @@ -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()

3
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java

@ -322,7 +322,6 @@ public interface ServerResponse { @@ -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 { @@ -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…
Cancel
Save