Provide simple way to create ClientResponse
This commit introduces ClientResponse.Builder, an easier way to create a ClientResponse from an existing response, or from scratch. Issue: SPR-16553
This commit is contained in:
parent
f8588e364a
commit
04c2a2990d
|
@ -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.
|
||||
|
@ -16,20 +16,26 @@
|
|||
|
||||
package org.springframework.web.reactive.function.client;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
|
||||
|
@ -68,6 +74,11 @@ public interface ClientResponse {
|
|||
*/
|
||||
MultiValueMap<String, ResponseCookie> cookies();
|
||||
|
||||
/**
|
||||
* Return the strategies used to convert the body of this response.
|
||||
*/
|
||||
ExchangeStrategies strategies();
|
||||
|
||||
/**
|
||||
* Extract the body with the given {@code BodyExtractor}.
|
||||
* @param extractor the {@code BodyExtractor} that reads from the response
|
||||
|
@ -141,6 +152,66 @@ public interface ClientResponse {
|
|||
<T> Mono<ResponseEntity<List<T>>> toEntityList(ParameterizedTypeReference<T> typeReference);
|
||||
|
||||
|
||||
// Static builder methods
|
||||
|
||||
/**
|
||||
* Create a builder with the status, headers, and cookies of the given response.
|
||||
* @param other the response to copy the status, headers, and cookies from
|
||||
* @return the created builder
|
||||
*/
|
||||
static Builder from(ClientResponse other) {
|
||||
Assert.notNull(other, "'other' 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.
|
||||
* @param statusCode the status code
|
||||
* @return the created builder
|
||||
*/
|
||||
static Builder create(HttpStatus statusCode) {
|
||||
return create(statusCode, ExchangeStrategies.withDefaults());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a response builder with the given status code and strategies for reading the body.
|
||||
* @param statusCode the status code
|
||||
* @param strategies the strategies
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a response builder with the given status code and message body readers.
|
||||
* @param statusCode the status code
|
||||
* @param messageReaders the message readers
|
||||
* @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()
|
||||
|
@ -172,4 +243,80 @@ public interface ClientResponse {
|
|||
HttpHeaders asHttpHeaders();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a builder for a response.
|
||||
*/
|
||||
interface Builder {
|
||||
|
||||
/**
|
||||
* Set the status code of the response.
|
||||
* @param statusCode the new status code.
|
||||
* @return this builder
|
||||
*/
|
||||
Builder statusCode(HttpStatus statusCode);
|
||||
|
||||
/**
|
||||
* Add the given header value(s) under the given name.
|
||||
* @param headerName the header name
|
||||
* @param headerValues the header value(s)
|
||||
* @return this builder
|
||||
* @see HttpHeaders#add(String, String)
|
||||
*/
|
||||
Builder header(String headerName, String... headerValues);
|
||||
|
||||
/**
|
||||
* Manipulate this response's headers with the given consumer. The
|
||||
* headers provided to the consumer are "live", so that the consumer can be used to
|
||||
* {@linkplain HttpHeaders#set(String, String) overwrite} existing header values,
|
||||
* {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other
|
||||
* {@link HttpHeaders} methods.
|
||||
* @param headersConsumer a function that consumes the {@code HttpHeaders}
|
||||
* @return this builder
|
||||
*/
|
||||
Builder headers(Consumer<HttpHeaders> headersConsumer);
|
||||
|
||||
/**
|
||||
* Add a cookie with the given name and value(s).
|
||||
* @param name the cookie name
|
||||
* @param values the cookie value(s)
|
||||
* @return this builder
|
||||
*/
|
||||
Builder cookie(String name, String... values);
|
||||
|
||||
/**
|
||||
* Manipulate this response's cookies with the given consumer. The
|
||||
* map provided to the consumer is "live", so that the consumer can be used to
|
||||
* {@linkplain MultiValueMap#set(Object, Object) overwrite} existing header values,
|
||||
* {@linkplain MultiValueMap#remove(Object) remove} values, or use any of the other
|
||||
* {@link MultiValueMap} methods.
|
||||
* @param cookiesConsumer a function that consumes the cookies map
|
||||
* @return this builder
|
||||
*/
|
||||
Builder cookies(Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer);
|
||||
|
||||
/**
|
||||
* Sets 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.
|
||||
* @return this builder
|
||||
*/
|
||||
Builder body(Flux<DataBuffer> body);
|
||||
|
||||
/**
|
||||
* Sets 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.
|
||||
* @param body the new body.
|
||||
* @return this builder
|
||||
*/
|
||||
Builder body(String body);
|
||||
|
||||
/**
|
||||
* Builds the response.
|
||||
* @return the response
|
||||
*/
|
||||
ClientResponse build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -61,6 +61,10 @@ class DefaultClientResponse implements ClientResponse {
|
|||
this.headers = new DefaultHeaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExchangeStrategies strategies() {
|
||||
return this.strategies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus statusCode() {
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function.client;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
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;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ClientResponse.Builder}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.0.5
|
||||
*/
|
||||
class DefaultClientResponseBuilder implements ClientResponse.Builder {
|
||||
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
private final MultiValueMap<String, ResponseCookie> cookies = new LinkedMultiValueMap<>();
|
||||
|
||||
private HttpStatus statusCode = HttpStatus.OK;
|
||||
|
||||
private Flux<DataBuffer> body = Flux.empty();
|
||||
|
||||
private ExchangeStrategies strategies;
|
||||
|
||||
|
||||
public DefaultClientResponseBuilder(ExchangeStrategies strategies) {
|
||||
Assert.notNull(strategies, "'strategies' must not be null");
|
||||
this.strategies = strategies;
|
||||
}
|
||||
|
||||
public DefaultClientResponseBuilder(ClientResponse other) {
|
||||
this(other.strategies());
|
||||
statusCode(other.statusCode());
|
||||
headers(headers -> headers.addAll(other.headers().asHttpHeaders()));
|
||||
cookies(cookies -> cookies.addAll(other.cookies()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultClientResponseBuilder statusCode(HttpStatus statusCode) {
|
||||
Assert.notNull(statusCode, "'statusCode' must not be null");
|
||||
this.statusCode = statusCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientResponse.Builder header(String headerName, String... headerValues) {
|
||||
for (String headerValue : headerValues) {
|
||||
this.headers.add(headerName, headerValue);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientResponse.Builder headers(Consumer<HttpHeaders> headersConsumer) {
|
||||
Assert.notNull(headersConsumer, "'headersConsumer' must not be null");
|
||||
headersConsumer.accept(this.headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultClientResponseBuilder cookie(String name, String... values) {
|
||||
for (String value : values) {
|
||||
this.cookies.add(name, ResponseCookie.from(name, value).build());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientResponse.Builder cookies(
|
||||
Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer) {
|
||||
Assert.notNull(cookiesConsumer, "'cookiesConsumer' 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");
|
||||
releaseBody();
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientResponse.Builder body(String body) {
|
||||
Assert.notNull(body, "'body' must not be null");
|
||||
releaseBody();
|
||||
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
|
||||
this.body = Flux.just(body).
|
||||
map(s -> {
|
||||
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
|
||||
return dataBufferFactory.wrap(bytes);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
private void releaseBody() {
|
||||
this.body.subscribe(DataBufferUtils.releaseConsumer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientResponse build() {
|
||||
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;
|
||||
|
||||
private final HttpHeaders headers;
|
||||
|
||||
private final MultiValueMap<String, ResponseCookie> cookies;
|
||||
|
||||
private final Flux<DataBuffer> body;
|
||||
|
||||
public BuiltClientHttpResponse(HttpStatus statusCode, HttpHeaders headers,
|
||||
MultiValueMap<String, ResponseCookie> cookies,
|
||||
Flux<DataBuffer> body) {
|
||||
|
||||
this.statusCode = statusCode;
|
||||
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
|
||||
this.cookies = unmodifiableCopy(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 HttpHeaders getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiValueMap<String, ResponseCookie> getCookies() {
|
||||
return this.cookies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<DataBuffer> getBody() {
|
||||
return this.body;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function.client.support;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.ExchangeStrategies;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link ClientResponse} interface that can be subclassed
|
||||
* to adapt the request in a
|
||||
* {@link org.springframework.web.reactive.function.client.ExchangeFilterFunction exchange filter function}.
|
||||
* All methods default to calling through to the wrapped request.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.0.5
|
||||
*/
|
||||
public class ClientResponseWrapper implements ClientResponse {
|
||||
|
||||
private final ClientResponse delegate;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code ClientResponseWrapper} that wraps the given response.
|
||||
* @param delegate the response to wrap
|
||||
*/
|
||||
public ClientResponseWrapper(ClientResponse delegate) {
|
||||
Assert.notNull(delegate, "'delegate' must not be null");
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the wrapped request.
|
||||
*/
|
||||
public ClientResponse response() {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExchangeStrategies strategies() {
|
||||
return this.delegate.strategies();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus statusCode() {
|
||||
return this.delegate.statusCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Headers headers() {
|
||||
return this.delegate.headers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiValueMap<String, ResponseCookie> cookies() {
|
||||
return this.delegate.cookies();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T body(BodyExtractor<T, ? super ClientHttpResponse> extractor) {
|
||||
return this.delegate.body(extractor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
|
||||
return this.delegate.bodyToMono(elementClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference) {
|
||||
return this.delegate.bodyToMono(typeReference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> bodyToFlux(Class<? extends T> elementClass) {
|
||||
return this.delegate.bodyToFlux(elementClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference) {
|
||||
return this.delegate.bodyToFlux(typeReference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType) {
|
||||
return this.delegate.toEntity(bodyType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<ResponseEntity<T>> toEntity(ParameterizedTypeReference<T> typeReference) {
|
||||
return this.delegate.toEntity(typeReference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> elementType) {
|
||||
return this.delegate.toEntityList(elementType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<ResponseEntity<List<T>>> toEntityList(ParameterizedTypeReference<T> typeReference) {
|
||||
return this.delegate.toEntityList(typeReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the {@code Headers} interface that can be subclassed
|
||||
* to adapt the headers in a
|
||||
* {@link org.springframework.web.reactive.function.client.ExchangeFilterFunction exchange filter function}.
|
||||
* All methods default to calling through to the wrapped request.
|
||||
*/
|
||||
public static class HeadersWrapper implements ClientResponse.Headers {
|
||||
|
||||
private final Headers headers;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code HeadersWrapper} that wraps the given request.
|
||||
* @param headers the headers to wrap
|
||||
*/
|
||||
public HeadersWrapper(Headers headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public OptionalLong contentLength() {
|
||||
return this.headers.contentLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MediaType> contentType() {
|
||||
return this.headers.contentType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> header(String headerName) {
|
||||
return this.headers.header(headerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders asHttpHeaders() {
|
||||
return this.headers.asHttpHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Classes supporting the {@code org.springframework.web.reactive.function.client} package.
|
||||
* Contains a {@code ClientResponse} wrapper to adapt a request.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.web.reactive.function.client.support;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
|
@ -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.
|
||||
|
@ -40,14 +40,14 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
|
|||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.util.UriBuilder;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link ServerRequest} interface that can be subclassed
|
||||
* to adapt the request to a {@link HandlerFunction handler function}.
|
||||
* to adapt the request in a
|
||||
* {@link org.springframework.web.reactive.function.server.HandlerFilterFunction handler filter function}.
|
||||
* All methods default to calling through to the wrapped request.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
|
@ -59,7 +59,7 @@ public class ServerRequestWrapper implements ServerRequest {
|
|||
|
||||
|
||||
/**
|
||||
* Create a new {@code RequestWrapper} that wraps the given request.
|
||||
* Create a new {@code ServerRequestWrapper} that wraps the given request.
|
||||
* @param delegate the request to wrap
|
||||
*/
|
||||
public ServerRequestWrapper(ServerRequest delegate) {
|
||||
|
@ -187,13 +187,15 @@ public class ServerRequestWrapper implements ServerRequest {
|
|||
|
||||
/**
|
||||
* Implementation of the {@code Headers} interface that can be subclassed
|
||||
* to adapt the headers to a {@link HandlerFunction handler function}.
|
||||
* to adapt the headers in a
|
||||
* {@link org.springframework.web.reactive.function.server.HandlerFilterFunction handler filter function}.
|
||||
* All methods default to calling through to the wrapped headers.
|
||||
*/
|
||||
public static class HeadersWrapper implements ServerRequest.Headers {
|
||||
|
||||
private final Headers headers;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code HeadersWrapper} that wraps the given request.
|
||||
* @param headers the headers to wrap
|
||||
|
@ -203,6 +205,7 @@ public class ServerRequestWrapper implements ServerRequest {
|
|||
this.headers = headers;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<MediaType> accept() {
|
||||
return this.headers.accept();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Classes supporting the {@code org.springframework.web.reactive.function} package.
|
||||
* Classes supporting the {@code org.springframework.web.reactive.function.server} package.
|
||||
* Contains a {@code HandlerAdapter} that supports {@code HandlerFunction}s,
|
||||
* a {@code HandlerResultHandler} that supports {@code ServerResponse}s, and
|
||||
* a {@code ServerRequest} wrapper to adapt a request.
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function.client;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class DefaultClientResponseBuilderTests {
|
||||
|
||||
private DataBufferFactory dataBufferFactory;
|
||||
|
||||
@Before
|
||||
public void createBufferFactory() {
|
||||
this.dataBufferFactory = new DefaultDataBufferFactory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normal() {
|
||||
Flux<DataBuffer> body = Flux.just("baz")
|
||||
.map(s -> s.getBytes(StandardCharsets.UTF_8))
|
||||
.map(dataBufferFactory::wrap);
|
||||
|
||||
ClientResponse response = ClientResponse.create(HttpStatus.BAD_GATEWAY, ExchangeStrategies.withDefaults())
|
||||
.header("foo", "bar")
|
||||
.cookie("baz", "qux")
|
||||
.body(body)
|
||||
.build();
|
||||
|
||||
assertEquals(HttpStatus.BAD_GATEWAY, response.statusCode());
|
||||
HttpHeaders responseHeaders = response.headers().asHttpHeaders();
|
||||
assertEquals("bar", responseHeaders.getFirst("foo"));
|
||||
assertNotNull("qux", response.cookies().getFirst("baz"));
|
||||
assertEquals("qux", response.cookies().getFirst("baz").getValue());
|
||||
|
||||
StepVerifier.create(response.bodyToFlux(String.class))
|
||||
.expectNext("baz")
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void from() throws Exception {
|
||||
Flux<DataBuffer> otherBody = Flux.just("foo", "bar")
|
||||
.map(s -> s.getBytes(StandardCharsets.UTF_8))
|
||||
.map(dataBufferFactory::wrap);
|
||||
|
||||
ClientResponse other = ClientResponse.create(HttpStatus.BAD_REQUEST, ExchangeStrategies.withDefaults())
|
||||
.header("foo", "bar")
|
||||
.cookie("baz", "qux")
|
||||
.body(otherBody)
|
||||
.build();
|
||||
|
||||
Flux<DataBuffer> body = Flux.just("baz")
|
||||
.map(s -> s.getBytes(StandardCharsets.UTF_8))
|
||||
.map(dataBufferFactory::wrap);
|
||||
|
||||
ClientResponse result = ClientResponse.from(other)
|
||||
.headers(httpHeaders -> httpHeaders.set("foo", "baar"))
|
||||
.cookies(cookies -> cookies.set("baz", ResponseCookie.from("baz", "quux").build()))
|
||||
.body(body)
|
||||
.build();
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, result.statusCode());
|
||||
assertEquals(1, result.headers().asHttpHeaders().size());
|
||||
assertEquals("baar", result.headers().asHttpHeaders().getFirst("foo"));
|
||||
assertEquals(1, result.cookies().size());
|
||||
assertEquals("quux", result.cookies().getFirst("baz").getValue());
|
||||
|
||||
StepVerifier.create(result.bodyToFlux(String.class))
|
||||
.expectNext("baz")
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function.client.support;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ReactiveHttpInputMessage;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
import org.springframework.web.reactive.function.BodyExtractors;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class ClientResponseWrapperTests {
|
||||
|
||||
private ClientResponse mockResponse;
|
||||
|
||||
private ClientResponseWrapper wrapper;
|
||||
|
||||
@Before
|
||||
public void createWrapper() {
|
||||
this.mockResponse = mock(ClientResponse.class);
|
||||
this.wrapper = new ClientResponseWrapper(mockResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void response() throws Exception {
|
||||
assertSame(mockResponse, wrapper.response());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void statusCode() throws Exception {
|
||||
HttpStatus status = HttpStatus.BAD_REQUEST;
|
||||
when(mockResponse.statusCode()).thenReturn(status);
|
||||
|
||||
assertSame(status, wrapper.statusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headers() throws Exception {
|
||||
ClientResponse.Headers headers = mock(ClientResponse.Headers.class);
|
||||
when(mockResponse.headers()).thenReturn(headers);
|
||||
|
||||
assertSame(headers, wrapper.headers());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cookies() throws Exception {
|
||||
MultiValueMap<String, ResponseCookie> cookies = mock(MultiValueMap.class);
|
||||
when(mockResponse.cookies()).thenReturn(cookies);
|
||||
|
||||
assertSame(cookies, wrapper.cookies());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bodyExtractor() throws Exception {
|
||||
Mono<String> result = Mono.just("foo");
|
||||
BodyExtractor<Mono<String>, ReactiveHttpInputMessage> extractor = BodyExtractors.toMono(String.class);
|
||||
when(mockResponse.body(extractor)).thenReturn(result);
|
||||
|
||||
assertSame(result, wrapper.body(extractor));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bodyToMonoClass() throws Exception {
|
||||
Mono<String> result = Mono.just("foo");
|
||||
when(mockResponse.bodyToMono(String.class)).thenReturn(result);
|
||||
|
||||
assertSame(result, wrapper.bodyToMono(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bodyToMonoParameterizedTypeReference() throws Exception {
|
||||
Mono<String> result = Mono.just("foo");
|
||||
ParameterizedTypeReference<String> reference = new ParameterizedTypeReference<String>() {};
|
||||
when(mockResponse.bodyToMono(reference)).thenReturn(result);
|
||||
|
||||
assertSame(result, wrapper.bodyToMono(reference));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bodyToFluxClass() throws Exception {
|
||||
Flux<String> result = Flux.just("foo");
|
||||
when(mockResponse.bodyToFlux(String.class)).thenReturn(result);
|
||||
|
||||
assertSame(result, wrapper.bodyToFlux(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bodyToFluxParameterizedTypeReference() throws Exception {
|
||||
Flux<String> result = Flux.just("foo");
|
||||
ParameterizedTypeReference<String> reference = new ParameterizedTypeReference<String>() {};
|
||||
when(mockResponse.bodyToFlux(reference)).thenReturn(result);
|
||||
|
||||
assertSame(result, wrapper.bodyToFlux(reference));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toEntityClass() throws Exception {
|
||||
Mono<ResponseEntity<String>> result = Mono.just(new ResponseEntity<>("foo", HttpStatus.OK));
|
||||
when(mockResponse.toEntity(String.class)).thenReturn(result);
|
||||
|
||||
assertSame(result, wrapper.toEntity(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toEntityParameterizedTypeReference() throws Exception {
|
||||
Mono<ResponseEntity<String>> result = Mono.just(new ResponseEntity<>("foo", HttpStatus.OK));
|
||||
ParameterizedTypeReference<String> reference = new ParameterizedTypeReference<String>() {};
|
||||
when(mockResponse.toEntity(reference)).thenReturn(result);
|
||||
|
||||
assertSame(result, wrapper.toEntity(reference));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toEntityListClass() throws Exception {
|
||||
Mono<ResponseEntity<List<String>>> result = Mono.just(new ResponseEntity<>(singletonList("foo"), HttpStatus.OK));
|
||||
when(mockResponse.toEntityList(String.class)).thenReturn(result);
|
||||
|
||||
assertSame(result, wrapper.toEntityList(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toEntityListParameterizedTypeReference() throws Exception {
|
||||
Mono<ResponseEntity<List<String>>> result = Mono.just(new ResponseEntity<>(singletonList("foo"), HttpStatus.OK));
|
||||
ParameterizedTypeReference<String> reference = new ParameterizedTypeReference<String>() {};
|
||||
when(mockResponse.toEntityList(reference)).thenReturn(result);
|
||||
|
||||
assertSame(result, wrapper.toEntityList(reference));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
@ -23,10 +23,17 @@ import java.util.Optional;
|
|||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpCookie;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ReactiveHttpInputMessage;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
import org.springframework.web.reactive.function.BodyExtractors;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
@ -128,4 +135,55 @@ public class ServerRequestWrapperTests {
|
|||
assertSame(pathVariables, wrapper.pathVariables());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cookies() throws Exception {
|
||||
MultiValueMap<String, HttpCookie> cookies = mock(MultiValueMap.class);
|
||||
when(mockRequest.cookies()).thenReturn(cookies);
|
||||
|
||||
assertSame(cookies, wrapper.cookies());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bodyExtractor() throws Exception {
|
||||
Mono<String> result = Mono.just("foo");
|
||||
BodyExtractor<Mono<String>, ReactiveHttpInputMessage> extractor = BodyExtractors.toMono(String.class);
|
||||
when(mockRequest.body(extractor)).thenReturn(result);
|
||||
|
||||
assertSame(result, wrapper.body(extractor));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bodyToMonoClass() throws Exception {
|
||||
Mono<String> result = Mono.just("foo");
|
||||
when(mockRequest.bodyToMono(String.class)).thenReturn(result);
|
||||
|
||||
assertSame(result, wrapper.bodyToMono(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bodyToMonoParameterizedTypeReference() throws Exception {
|
||||
Mono<String> result = Mono.just("foo");
|
||||
ParameterizedTypeReference<String> reference = new ParameterizedTypeReference<String>() {};
|
||||
when(mockRequest.bodyToMono(reference)).thenReturn(result);
|
||||
|
||||
assertSame(result, wrapper.bodyToMono(reference));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bodyToFluxClass() throws Exception {
|
||||
Flux<String> result = Flux.just("foo");
|
||||
when(mockRequest.bodyToFlux(String.class)).thenReturn(result);
|
||||
|
||||
assertSame(result, wrapper.bodyToFlux(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bodyToFluxParameterizedTypeReference() throws Exception {
|
||||
Flux<String> result = Flux.just("foo");
|
||||
ParameterizedTypeReference<String> reference = new ParameterizedTypeReference<String>() {};
|
||||
when(mockRequest.bodyToFlux(reference)).thenReturn(result);
|
||||
|
||||
assertSame(result, wrapper.bodyToFlux(reference));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue