Moved BodyExtractor and BodyInserter to http.codec
This commit moves the web.reactive.function.[BodyInserter|BodyExtractor] to http.codec, so that they can be used from the client as well. Furthermore, it parameterized both inserter and extractor over ReactiveHttpOutputMessage and ReactiveHttpInputMessage respectively, so that they can be limited to only be used on the client or server.
This commit is contained in:
@@ -24,11 +24,15 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRange;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.BodyExtractor;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
@@ -68,8 +72,14 @@ class DefaultRequest implements Request {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T body(BodyExtractor<T> extractor) {
|
||||
return extractor.extract(request(), this.strategies);
|
||||
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor) {
|
||||
return extractor.extract(request(),
|
||||
new BodyExtractor.Context() {
|
||||
@Override
|
||||
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
|
||||
return DefaultRequest.this.strategies.messageReaders();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -41,6 +41,8 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.BodyInserter;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -142,7 +144,7 @@ class DefaultResponseBuilder implements Response.BodyBuilder {
|
||||
@Override
|
||||
public Response<Void> build() {
|
||||
return body(BodyInserter.of(
|
||||
(response, strategies) -> response.setComplete(),
|
||||
(response, context) -> response.setComplete(),
|
||||
() -> null));
|
||||
}
|
||||
|
||||
@@ -150,18 +152,18 @@ class DefaultResponseBuilder implements Response.BodyBuilder {
|
||||
public <T extends Publisher<Void>> Response<T> build(T voidPublisher) {
|
||||
Assert.notNull(voidPublisher, "'voidPublisher' must not be null");
|
||||
return body(BodyInserter.of(
|
||||
(response, strategies) -> Flux.from(voidPublisher).then(response.setComplete()),
|
||||
(response, context) -> Flux.from(voidPublisher).then(response.setComplete()),
|
||||
() -> null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Response<T> body(BiFunction<ServerHttpResponse, StrategiesSupplier, Mono<Void>> writer,
|
||||
public <T> Response<T> body(BiFunction<ServerHttpResponse, BodyInserter.Context, Mono<Void>> writer,
|
||||
Supplier<T> supplier) {
|
||||
return body(BodyInserter.of(writer, supplier));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Response<T> body(BodyInserter<T> inserter) {
|
||||
public <T> Response<T> body(BodyInserter<T, ? super ServerHttpResponse> inserter) {
|
||||
Assert.notNull(inserter, "'inserter' must not be null");
|
||||
return new BodyInserterResponse<T>(this.statusCode, this.headers, inserter);
|
||||
}
|
||||
@@ -235,11 +237,12 @@ class DefaultResponseBuilder implements Response.BodyBuilder {
|
||||
|
||||
private static final class BodyInserterResponse<T> extends AbstractResponse<T> {
|
||||
|
||||
private final BodyInserter<T> inserter;
|
||||
private final BodyInserter<T, ? super ServerHttpResponse> inserter;
|
||||
|
||||
|
||||
public BodyInserterResponse(
|
||||
int statusCode, HttpHeaders headers, BodyInserter<T> inserter) {
|
||||
public BodyInserterResponse(int statusCode, HttpHeaders headers,
|
||||
BodyInserter<T, ? super ServerHttpResponse> inserter) {
|
||||
|
||||
super(statusCode, headers);
|
||||
this.inserter = inserter;
|
||||
}
|
||||
@@ -253,7 +256,12 @@ class DefaultResponseBuilder implements Response.BodyBuilder {
|
||||
public Mono<Void> writeTo(ServerWebExchange exchange, StrategiesSupplier strategies) {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
writeStatusAndHeaders(response);
|
||||
return this.inserter.insert(response, strategies);
|
||||
return this.inserter.insert(response, new BodyInserter.Context() {
|
||||
@Override
|
||||
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
|
||||
return strategies.messageWriters();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRange;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.BodyExtractor;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
|
||||
/**
|
||||
* Represents an HTTP request, as handled by a {@code HandlerFunction}.
|
||||
@@ -67,7 +69,7 @@ public interface Request {
|
||||
* @param <T> the type of the body returned
|
||||
* @return the extracted body
|
||||
*/
|
||||
<T> T body(BodyExtractor<T> extractor);
|
||||
<T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor);
|
||||
|
||||
/**
|
||||
* Return the request attribute value if present.
|
||||
|
||||
@@ -27,6 +27,8 @@ import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.BodyExtractor;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.PathMatcher;
|
||||
@@ -314,7 +316,7 @@ public abstract class RequestPredicates {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T body(BodyExtractor<T> extractor) {
|
||||
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor) {
|
||||
return this.request.body(extractor);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.BodyInserter;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
@@ -316,7 +317,7 @@ public interface Response<T> {
|
||||
* @param <T> the type contained in the body
|
||||
* @return the built response
|
||||
*/
|
||||
<T> Response<T> body(BiFunction<ServerHttpResponse, StrategiesSupplier, Mono<Void>> writer,
|
||||
<T> Response<T> body(BiFunction<ServerHttpResponse, BodyInserter.Context, Mono<Void>> writer,
|
||||
Supplier<T> supplier);
|
||||
|
||||
/**
|
||||
@@ -325,7 +326,7 @@ public interface Response<T> {
|
||||
* @param <T> the type contained in the body
|
||||
* @return the built response
|
||||
*/
|
||||
<T> Response<T> body(BodyInserter<T> inserter);
|
||||
<T> Response<T> body(BodyInserter<T, ? super ServerHttpResponse> inserter);
|
||||
|
||||
/**
|
||||
* Render the template with the given {@code name} using the given {@code modelAttributes}.
|
||||
|
||||
@@ -28,8 +28,9 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRange;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.BodyExtractor;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
import org.springframework.web.reactive.function.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.Request;
|
||||
|
||||
@@ -83,7 +84,7 @@ public class RequestWrapper implements Request {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T body(BodyExtractor<T> extractor) {
|
||||
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor) {
|
||||
return this.request.body(extractor);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.web.reactive.function.BodyExtractors.toMono;
|
||||
import static org.springframework.http.codec.BodyExtractors.toMono;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.BodyInserter;
|
||||
import org.springframework.http.codec.EncoderHttpMessageWriter;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
@@ -217,7 +218,7 @@ public class DefaultResponseBuilderTests {
|
||||
public void bodyInserter() throws Exception {
|
||||
String body = "foo";
|
||||
Supplier<String> supplier = () -> body;
|
||||
BiFunction<ServerHttpResponse, StrategiesSupplier, Mono<Void>> writer =
|
||||
BiFunction<ServerHttpResponse, BodyInserter.Context, Mono<Void>> writer =
|
||||
(response, strategies) -> {
|
||||
byte[] bodyBytes = body.getBytes(UTF_8);
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(bodyBytes);
|
||||
|
||||
@@ -50,6 +50,7 @@ import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.springframework.http.codec.BodyInserters.fromPublisher;
|
||||
import static org.springframework.web.reactive.function.RouterFunctions.route;
|
||||
|
||||
/**
|
||||
@@ -155,14 +156,14 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr
|
||||
|
||||
public Response<Publisher<Person>> mono(Request request) {
|
||||
Person person = new Person("John");
|
||||
return Response.ok().body(BodyInserters.fromPublisher(Mono.just(person), Person.class));
|
||||
return Response.ok().body(fromPublisher(Mono.just(person), Person.class));
|
||||
}
|
||||
|
||||
public Response<Publisher<Person>> flux(Request request) {
|
||||
Person person1 = new Person("John");
|
||||
Person person2 = new Person("Jane");
|
||||
return Response.ok().body(
|
||||
BodyInserters.fromPublisher(Flux.just(person1, person2), Person.class));
|
||||
fromPublisher(Flux.just(person1, person2), Person.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRange;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.BodyExtractor;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
@@ -88,9 +90,9 @@ public class MockRequest<T> implements Request {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <S> S body(BodyExtractor<S> extractor) {
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S> S body(BodyExtractor<S, ? super ServerHttpRequest> extractor){
|
||||
return (S) this.body;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.springframework.web.reactive.function.BodyExtractors.toMono;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromPublisher;
|
||||
import static org.springframework.http.codec.BodyExtractors.toMono;
|
||||
import static org.springframework.http.codec.BodyInserters.fromPublisher;
|
||||
import static org.springframework.web.reactive.function.RequestPredicates.GET;
|
||||
import static org.springframework.web.reactive.function.RequestPredicates.POST;
|
||||
import static org.springframework.web.reactive.function.RouterFunctions.route;
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.junit.Test;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
|
||||
import static org.springframework.http.codec.BodyInserters.fromObject;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.tests.TestSubscriber;
|
||||
import org.springframework.web.client.reactive.WebClient;
|
||||
|
||||
import static org.springframework.http.codec.BodyInserters.fromServerSentEvents;
|
||||
import static org.springframework.web.client.reactive.ClientWebRequestBuilders.get;
|
||||
import static org.springframework.web.client.reactive.ResponseExtractors.bodyStream;
|
||||
import static org.springframework.web.reactive.function.RouterFunctions.route;
|
||||
@@ -111,13 +112,13 @@ public class SseHandlerFunctionIntegrationTests
|
||||
|
||||
public Response<Publisher<String>> string(Request request) {
|
||||
Flux<String> flux = Flux.interval(Duration.ofMillis(100)).map(l -> "foo " + l).take(2);
|
||||
return Response.ok().body(BodyInserters.fromServerSentEvents(flux, String.class));
|
||||
return Response.ok().body(fromServerSentEvents(flux, String.class));
|
||||
}
|
||||
|
||||
public Response<Publisher<Person>> person(Request request) {
|
||||
Flux<Person> flux = Flux.interval(Duration.ofMillis(100))
|
||||
.map(l -> new Person("foo " + l)).take(2);
|
||||
return Response.ok().body(BodyInserters.fromServerSentEvents(flux, Person.class));
|
||||
return Response.ok().body(fromServerSentEvents(flux, Person.class));
|
||||
}
|
||||
|
||||
public Response<Publisher<ServerSentEvent<String>>> sse(Request request) {
|
||||
@@ -127,7 +128,7 @@ public class SseHandlerFunctionIntegrationTests
|
||||
.comment("bar")
|
||||
.build()).take(2);
|
||||
|
||||
return Response.ok().body(BodyInserters.fromServerSentEvents(flux));
|
||||
return Response.ok().body(fromServerSentEvents(flux));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,28 +14,43 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function;
|
||||
package org.springframework.http.codec;
|
||||
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.http.ReactiveHttpInputMessage;
|
||||
|
||||
/**
|
||||
* A function that can extract data from a {@link Request} body.
|
||||
* A function that can extract data from a {@link ReactiveHttpInputMessage} body.
|
||||
*
|
||||
* @param <T> the type of data to extract
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.0
|
||||
* @see Request#body(BodyExtractor)
|
||||
* @see BodyExtractors
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BodyExtractor<T> {
|
||||
public interface BodyExtractor<T, M extends ReactiveHttpInputMessage> {
|
||||
|
||||
/**
|
||||
* Extract from the given request.
|
||||
* @param request the request to extract from
|
||||
* @param strategies the strategies to use
|
||||
* @param inputMessage request to extract from
|
||||
* @param context the configuration to use
|
||||
* @return the extracted data
|
||||
*/
|
||||
T extract(ServerHttpRequest request, StrategiesSupplier strategies);
|
||||
T extract(M inputMessage, Context context);
|
||||
|
||||
/**
|
||||
* Defines the context used during the extraction.
|
||||
*/
|
||||
interface Context {
|
||||
|
||||
/**
|
||||
* Supply a {@linkplain Stream stream} of {@link HttpMessageReader}s to be used for body
|
||||
* extraction.
|
||||
* @return the stream of message readers
|
||||
*/
|
||||
Supplier<Stream<HttpMessageReader<?>>> messageReaders();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function;
|
||||
package org.springframework.http.codec;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -28,11 +28,10 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.ReactiveHttpInputMessage;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
|
||||
|
||||
/**
|
||||
* Implementations of {@link BodyExtractor} that read various bodies, such a reactive streams.
|
||||
@@ -48,7 +47,7 @@ public abstract class BodyExtractors {
|
||||
* @param <T> the element type
|
||||
* @return a {@code BodyExtractor} that reads a mono
|
||||
*/
|
||||
public static <T> BodyExtractor<Mono<T>> toMono(Class<? extends T> elementClass) {
|
||||
public static <T> BodyExtractor<Mono<T>, ReactiveHttpInputMessage> toMono(Class<? extends T> elementClass) {
|
||||
Assert.notNull(elementClass, "'elementClass' must not be null");
|
||||
return toMono(ResolvableType.forClass(elementClass));
|
||||
}
|
||||
@@ -59,9 +58,9 @@ public abstract class BodyExtractors {
|
||||
* @param <T> the element type
|
||||
* @return a {@code BodyExtractor} that reads a mono
|
||||
*/
|
||||
public static <T> BodyExtractor<Mono<T>> toMono(ResolvableType elementType) {
|
||||
public static <T> BodyExtractor<Mono<T>, ReactiveHttpInputMessage> toMono(ResolvableType elementType) {
|
||||
Assert.notNull(elementType, "'elementType' must not be null");
|
||||
return (request, strategies) -> readWithMessageReaders(request, strategies,
|
||||
return (request, context) -> readWithMessageReaders(request, context,
|
||||
elementType,
|
||||
reader -> reader.readMono(elementType, request, Collections.emptyMap()),
|
||||
Mono::error);
|
||||
@@ -73,7 +72,7 @@ public abstract class BodyExtractors {
|
||||
* @param <T> the element type
|
||||
* @return a {@code BodyExtractor} that reads a mono
|
||||
*/
|
||||
public static <T> BodyExtractor<Flux<T>> toFlux(Class<? extends T> elementClass) {
|
||||
public static <T> BodyExtractor<Flux<T>, ReactiveHttpInputMessage> toFlux(Class<? extends T> elementClass) {
|
||||
Assert.notNull(elementClass, "'elementClass' must not be null");
|
||||
return toFlux(ResolvableType.forClass(elementClass));
|
||||
}
|
||||
@@ -84,23 +83,23 @@ public abstract class BodyExtractors {
|
||||
* @param <T> the element type
|
||||
* @return a {@code BodyExtractor} that reads a mono
|
||||
*/
|
||||
public static <T> BodyExtractor<Flux<T>> toFlux(ResolvableType elementType) {
|
||||
public static <T> BodyExtractor<Flux<T>, ReactiveHttpInputMessage> toFlux(ResolvableType elementType) {
|
||||
Assert.notNull(elementType, "'elementType' must not be null");
|
||||
return (request, strategies) -> readWithMessageReaders(request, strategies,
|
||||
return (request, context) -> readWithMessageReaders(request, context,
|
||||
elementType,
|
||||
reader -> reader.read(elementType, request, Collections.emptyMap()),
|
||||
Flux::error);
|
||||
}
|
||||
|
||||
private static <T, S extends Publisher<T>> S readWithMessageReaders(
|
||||
ServerHttpRequest request,
|
||||
StrategiesSupplier strategies,
|
||||
ReactiveHttpInputMessage inputMessage,
|
||||
BodyExtractor.Context context,
|
||||
ResolvableType elementType,
|
||||
Function<HttpMessageReader<T>, S> readerFunction,
|
||||
Function<Throwable, S> unsupportedError) {
|
||||
|
||||
MediaType contentType = contentType(request);
|
||||
Supplier<Stream<HttpMessageReader<?>>> messageReaders = strategies.messageReaders();
|
||||
MediaType contentType = contentType(inputMessage);
|
||||
Supplier<Stream<HttpMessageReader<?>>> messageReaders = context.messageReaders();
|
||||
return messageReaders.get()
|
||||
.filter(r -> r.canRead(elementType, contentType))
|
||||
.findFirst()
|
||||
@@ -110,14 +109,14 @@ public abstract class BodyExtractors {
|
||||
List<MediaType> supportedMediaTypes = messageReaders.get()
|
||||
.flatMap(reader -> reader.getReadableMediaTypes().stream())
|
||||
.collect(Collectors.toList());
|
||||
UnsupportedMediaTypeStatusException error =
|
||||
new UnsupportedMediaTypeStatusException(contentType, supportedMediaTypes);
|
||||
UnsupportedMediaTypeException error =
|
||||
new UnsupportedMediaTypeException(contentType, supportedMediaTypes);
|
||||
return unsupportedError.apply(error);
|
||||
});
|
||||
}
|
||||
|
||||
private static MediaType contentType(ServerHttpRequest request) {
|
||||
MediaType result = request.getHeaders().getContentType();
|
||||
private static MediaType contentType(HttpMessage message) {
|
||||
MediaType result = message.getHeaders().getContentType();
|
||||
return result != null ? result : MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
|
||||
@@ -14,35 +14,32 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function;
|
||||
package org.springframework.http.codec;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.http.ReactiveHttpOutputMessage;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A component that can insert data into a {@link Response} body.
|
||||
* A combination of functions that can populate a {@link ReactiveHttpOutputMessage} body.
|
||||
*
|
||||
* @param <T> the type of data to insert
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.0
|
||||
* @see Response#body()
|
||||
* @see Response.BodyBuilder#body(BodyInserter)
|
||||
* @see BodyInserters
|
||||
*/
|
||||
public interface BodyInserter<T> {
|
||||
public interface BodyInserter<T, M extends ReactiveHttpOutputMessage> {
|
||||
|
||||
/**
|
||||
* Insert into the given response.
|
||||
* @param response the response to insert into
|
||||
* @param strategies the strategies to use
|
||||
* @param outputMessage the response to insert into
|
||||
* @param context the context to use
|
||||
* @return a {@code Mono} that indicates completion or error
|
||||
*/
|
||||
Mono<Void> insert(ServerHttpResponse response, StrategiesSupplier strategies);
|
||||
Mono<Void> insert(M outputMessage, Context context);
|
||||
|
||||
/**
|
||||
* Return the type contained in the body.
|
||||
@@ -50,7 +47,6 @@ public interface BodyInserter<T> {
|
||||
*/
|
||||
T t();
|
||||
|
||||
|
||||
/**
|
||||
* Return a new {@code BodyInserter} described by the given writer and supplier functions.
|
||||
* @param writer the writer function for the new inserter
|
||||
@@ -58,13 +54,29 @@ public interface BodyInserter<T> {
|
||||
* @param <T> the type supplied and written by the inserter
|
||||
* @return the new {@code BodyInserter}
|
||||
*/
|
||||
static <T> BodyInserter<T> of(BiFunction<ServerHttpResponse, StrategiesSupplier, Mono<Void>> writer,
|
||||
static <T, M extends ReactiveHttpOutputMessage> BodyInserter<T, M> of(
|
||||
BiFunction<M, Context, Mono<Void>> writer,
|
||||
Supplier<T> supplier) {
|
||||
|
||||
Assert.notNull(writer, "'writer' must not be null");
|
||||
Assert.notNull(supplier, "'supplier' must not be null");
|
||||
|
||||
return new BodyInserters.DefaultBodyInserter<T>(writer, supplier);
|
||||
return new BodyInserters.DefaultBodyInserter<T, M>(writer, supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the context used during the insertion.
|
||||
*/
|
||||
interface Context {
|
||||
|
||||
/**
|
||||
* Supply a {@linkplain Stream stream} of {@link HttpMessageWriter}s to be used for response
|
||||
* body conversion.
|
||||
* @return the stream of message writers
|
||||
*/
|
||||
Supplier<Stream<HttpMessageWriter<?>>> messageWriters();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -14,23 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function;
|
||||
package org.springframework.http.codec;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.codec.ResourceHttpMessageWriter;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.http.codec.ServerSentEventHttpMessageWriter;
|
||||
import org.springframework.http.ReactiveHttpOutputMessage;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -61,10 +60,10 @@ public abstract class BodyInserters {
|
||||
* @param body the body of the response
|
||||
* @return a {@code BodyInserter} that writes a single object
|
||||
*/
|
||||
public static <T> BodyInserter<T> fromObject(T body) {
|
||||
public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromObject(T body) {
|
||||
Assert.notNull(body, "'body' must not be null");
|
||||
return BodyInserter.of(
|
||||
(response, strategies) -> writeWithMessageWriters(response, strategies,
|
||||
(response, context) -> writeWithMessageWriters(response, context,
|
||||
Mono.just(body), ResolvableType.forInstance(body)),
|
||||
() -> body);
|
||||
}
|
||||
@@ -74,10 +73,10 @@ public abstract class BodyInserters {
|
||||
* @param publisher the publisher to stream to the response body
|
||||
* @param elementClass the class of elements contained in the publisher
|
||||
* @param <T> the type of the elements contained in the publisher
|
||||
* @param <S> the type of the {@code Publisher}.
|
||||
* @param <S> the type of the {@code Publisher}
|
||||
* @return a {@code BodyInserter} that writes a {@code Publisher}
|
||||
*/
|
||||
public static <S extends Publisher<T>, T> BodyInserter<S> fromPublisher(S publisher,
|
||||
public static <S extends Publisher<T>, T> BodyInserter<S, ReactiveHttpOutputMessage> fromPublisher(S publisher,
|
||||
Class<T> elementClass) {
|
||||
|
||||
Assert.notNull(publisher, "'publisher' must not be null");
|
||||
@@ -90,16 +89,16 @@ public abstract class BodyInserters {
|
||||
* @param publisher the publisher to stream to the response body
|
||||
* @param elementType the type of elements contained in the publisher
|
||||
* @param <T> the type of the elements contained in the publisher
|
||||
* @param <S> the type of the {@code Publisher}.
|
||||
* @param <S> the type of the {@code Publisher}
|
||||
* @return a {@code BodyInserter} that writes a {@code Publisher}
|
||||
*/
|
||||
public static <S extends Publisher<T>, T> BodyInserter<S> fromPublisher(S publisher,
|
||||
public static <S extends Publisher<T>, T> BodyInserter<S, ReactiveHttpOutputMessage> fromPublisher(S publisher,
|
||||
ResolvableType elementType) {
|
||||
|
||||
Assert.notNull(publisher, "'publisher' must not be null");
|
||||
Assert.notNull(elementType, "'elementType' must not be null");
|
||||
return BodyInserter.of(
|
||||
(response, strategies) -> writeWithMessageWriters(response, strategies,
|
||||
(response, context) -> writeWithMessageWriters(response, context,
|
||||
publisher, elementType),
|
||||
() -> publisher
|
||||
);
|
||||
@@ -114,10 +113,10 @@ public abstract class BodyInserters {
|
||||
* @param <T> the type of the {@code Resource}
|
||||
* @return a {@code BodyInserter} that writes a {@code Publisher}
|
||||
*/
|
||||
public static <T extends Resource> BodyInserter<T> fromResource(T resource) {
|
||||
public static <T extends Resource> BodyInserter<T, ReactiveHttpOutputMessage> fromResource(T resource) {
|
||||
Assert.notNull(resource, "'resource' must not be null");
|
||||
return BodyInserter.of(
|
||||
(response, strategies) -> {
|
||||
(response, context) -> {
|
||||
ResourceHttpMessageWriter messageWriter = new ResourceHttpMessageWriter();
|
||||
MediaType contentType = response.getHeaders().getContentType();
|
||||
return messageWriter.write(Mono.just(resource), RESOURCE_TYPE, contentType,
|
||||
@@ -134,12 +133,12 @@ public abstract class BodyInserters {
|
||||
* @return a {@code BodyInserter} that writes a {@code ServerSentEvent} publisher
|
||||
* @see <a href="https://www.w3.org/TR/eventsource/">Server-Sent Events W3C recommendation</a>
|
||||
*/
|
||||
public static <T, S extends Publisher<ServerSentEvent<T>>> BodyInserter<S> fromServerSentEvents(
|
||||
public static <T, S extends Publisher<ServerSentEvent<T>>> BodyInserter<S, ServerHttpResponse> fromServerSentEvents(
|
||||
S eventsPublisher) {
|
||||
|
||||
Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null");
|
||||
return BodyInserter.of(
|
||||
(response, strategies) -> {
|
||||
(response, context) -> {
|
||||
ServerSentEventHttpMessageWriter messageWriter = sseMessageWriter();
|
||||
MediaType contentType = response.getHeaders().getContentType();
|
||||
return messageWriter.write(eventsPublisher, SERVER_SIDE_EVENT_TYPE,
|
||||
@@ -159,7 +158,7 @@ public abstract class BodyInserters {
|
||||
* Server-Sent Events
|
||||
* @see <a href="https://www.w3.org/TR/eventsource/">Server-Sent Events W3C recommendation</a>
|
||||
*/
|
||||
public static <T, S extends Publisher<T>> BodyInserter<S> fromServerSentEvents(S eventsPublisher,
|
||||
public static <T, S extends Publisher<T>> BodyInserter<S, ServerHttpResponse> fromServerSentEvents(S eventsPublisher,
|
||||
Class<T> eventClass) {
|
||||
|
||||
Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null");
|
||||
@@ -177,13 +176,13 @@ public abstract class BodyInserters {
|
||||
* Server-Sent Events
|
||||
* @see <a href="https://www.w3.org/TR/eventsource/">Server-Sent Events W3C recommendation</a>
|
||||
*/
|
||||
public static <T, S extends Publisher<T>> BodyInserter<S> fromServerSentEvents(S eventsPublisher,
|
||||
public static <T, S extends Publisher<T>> BodyInserter<S, ServerHttpResponse> fromServerSentEvents(S eventsPublisher,
|
||||
ResolvableType eventType) {
|
||||
|
||||
Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null");
|
||||
Assert.notNull(eventType, "'eventType' must not be null");
|
||||
return BodyInserter.of(
|
||||
(response, strategies) -> {
|
||||
(response, context) -> {
|
||||
ServerSentEventHttpMessageWriter messageWriter = sseMessageWriter();
|
||||
MediaType contentType = response.getHeaders().getContentType();
|
||||
return messageWriter.write(eventsPublisher, eventType, contentType, response,
|
||||
@@ -200,23 +199,27 @@ public abstract class BodyInserters {
|
||||
new ServerSentEventHttpMessageWriter();
|
||||
}
|
||||
|
||||
private static <T> Mono<Void> writeWithMessageWriters(ServerHttpResponse response,
|
||||
StrategiesSupplier strategies,
|
||||
private static <T> Mono<Void> writeWithMessageWriters(ReactiveHttpOutputMessage outputMessage,
|
||||
BodyInserter.Context context,
|
||||
Publisher<T> body,
|
||||
ResolvableType bodyType) {
|
||||
|
||||
// TODO: use ContentNegotiatingResultHandlerSupport
|
||||
MediaType contentType = response.getHeaders().getContentType();
|
||||
return strategies.messageWriters().get()
|
||||
MediaType contentType = outputMessage.getHeaders().getContentType();
|
||||
Supplier<Stream<HttpMessageWriter<?>>> messageWriters = context.messageWriters();
|
||||
return messageWriters.get()
|
||||
.filter(messageWriter -> messageWriter.canWrite(bodyType, contentType))
|
||||
.findFirst()
|
||||
.map(BodyInserters::cast)
|
||||
.map(messageWriter -> messageWriter
|
||||
.write(body, bodyType, contentType, response, Collections
|
||||
.write(body, bodyType, contentType, outputMessage, Collections
|
||||
.emptyMap()))
|
||||
.orElseGet(() -> {
|
||||
response.setStatusCode(HttpStatus.NOT_ACCEPTABLE);
|
||||
return response.setComplete();
|
||||
List<MediaType> supportedMediaTypes = messageWriters.get()
|
||||
.flatMap(reader -> reader.getWritableMediaTypes().stream())
|
||||
.collect(Collectors.toList());
|
||||
UnsupportedMediaTypeException error =
|
||||
new UnsupportedMediaTypeException(contentType, supportedMediaTypes);
|
||||
return Mono.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -225,22 +228,23 @@ public abstract class BodyInserters {
|
||||
return (HttpMessageWriter<T>) messageWriter;
|
||||
}
|
||||
|
||||
static class DefaultBodyInserter<T> implements BodyInserter<T> {
|
||||
static class DefaultBodyInserter<T, M extends ReactiveHttpOutputMessage>
|
||||
implements BodyInserter<T, M> {
|
||||
|
||||
private final BiFunction<ServerHttpResponse, StrategiesSupplier, Mono<Void>> writer;
|
||||
private final BiFunction<M, Context, Mono<Void>> writer;
|
||||
|
||||
private final Supplier<T> supplier;
|
||||
|
||||
public DefaultBodyInserter(
|
||||
BiFunction<ServerHttpResponse, StrategiesSupplier, Mono<Void>> writer,
|
||||
BiFunction<M, Context, Mono<Void>> writer,
|
||||
Supplier<T> supplier) {
|
||||
this.writer = writer;
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> insert(ServerHttpResponse response, StrategiesSupplier strategies) {
|
||||
return this.writer.apply(response, strategies);
|
||||
public Mono<Void> insert(M outputMessage, Context context) {
|
||||
return this.writer.apply(outputMessage, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.http.codec;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* Exception thrown to indicate that a {@code Content-Type} is not supported.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class UnsupportedMediaTypeException extends NestedRuntimeException {
|
||||
|
||||
private final MediaType contentType;
|
||||
|
||||
private final List<MediaType> supportedMediaTypes;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor for when the specified Content-Type is invalid.
|
||||
*/
|
||||
public UnsupportedMediaTypeException(String reason) {
|
||||
super(reason);
|
||||
this.contentType = null;
|
||||
this.supportedMediaTypes = Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for when the Content-Type can be parsed but is not supported.
|
||||
*/
|
||||
public UnsupportedMediaTypeException(MediaType contentType, List<MediaType> supportedMediaTypes) {
|
||||
super("Content type '" + contentType + "' not supported");
|
||||
this.contentType = contentType;
|
||||
this.supportedMediaTypes = Collections.unmodifiableList(supportedMediaTypes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the request Content-Type header if it was parsed successfully.
|
||||
*/
|
||||
public Optional<MediaType> getContentType() {
|
||||
return Optional.ofNullable(this.contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of supported content types in cases when the Content-Type
|
||||
* header is parsed but not supported, or an empty list otherwise.
|
||||
*/
|
||||
public List<MediaType> getSupportedMediaTypes() {
|
||||
return this.supportedMediaTypes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,31 +14,60 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function;
|
||||
package org.springframework.http.codec;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.codec.ByteBufferDecoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DefaultDataBuffer;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ReactiveHttpInputMessage;
|
||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.tests.TestSubscriber;
|
||||
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class BodyExtractorsTests {
|
||||
|
||||
private BodyExtractor.Context context;
|
||||
|
||||
@Before
|
||||
public void createContext() {
|
||||
final List<HttpMessageReader<?>> messageReaders = new ArrayList<>();
|
||||
messageReaders.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
|
||||
messageReaders.add(new DecoderHttpMessageReader<>(new StringDecoder()));
|
||||
messageReaders.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
|
||||
messageReaders.add(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
|
||||
|
||||
this.context = new BodyExtractor.Context() {
|
||||
@Override
|
||||
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
|
||||
return messageReaders::stream;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toMono() throws Exception {
|
||||
BodyExtractor<Mono<String>> extractor = BodyExtractors.toMono(String.class);
|
||||
BodyExtractor<Mono<String>, ReactiveHttpInputMessage> extractor = BodyExtractors.toMono(String.class);
|
||||
|
||||
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
|
||||
DefaultDataBuffer dataBuffer =
|
||||
@@ -48,9 +77,7 @@ public class BodyExtractorsTests {
|
||||
MockServerHttpRequest request = new MockServerHttpRequest();
|
||||
request.setBody(body);
|
||||
|
||||
StrategiesSupplier strategies = StrategiesSupplier.builder().build();
|
||||
|
||||
Mono<String> result = extractor.extract(request, strategies);
|
||||
Mono<String> result = extractor.extract(request, this.context);
|
||||
|
||||
TestSubscriber.subscribe(result)
|
||||
.assertComplete()
|
||||
@@ -59,7 +86,7 @@ public class BodyExtractorsTests {
|
||||
|
||||
@Test
|
||||
public void toFlux() throws Exception {
|
||||
BodyExtractor<Flux<String>> extractor = BodyExtractors.toFlux(String.class);
|
||||
BodyExtractor<Flux<String>, ReactiveHttpInputMessage> extractor = BodyExtractors.toFlux(String.class);
|
||||
|
||||
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
|
||||
DefaultDataBuffer dataBuffer =
|
||||
@@ -69,9 +96,7 @@ public class BodyExtractorsTests {
|
||||
MockServerHttpRequest request = new MockServerHttpRequest();
|
||||
request.setBody(body);
|
||||
|
||||
StrategiesSupplier strategies = StrategiesSupplier.builder().build();
|
||||
|
||||
Flux<String> result = extractor.extract(request, strategies);
|
||||
Flux<String> result = extractor.extract(request, this.context);
|
||||
TestSubscriber.subscribe(result)
|
||||
.assertComplete()
|
||||
.assertValues("foo");
|
||||
@@ -79,7 +104,7 @@ public class BodyExtractorsTests {
|
||||
|
||||
@Test
|
||||
public void toFluxUnacceptable() throws Exception {
|
||||
BodyExtractor<Flux<String>> extractor = BodyExtractors.toFlux(String.class);
|
||||
BodyExtractor<Flux<String>, ReactiveHttpInputMessage> extractor = BodyExtractors.toFlux(String.class);
|
||||
|
||||
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
|
||||
DefaultDataBuffer dataBuffer =
|
||||
@@ -90,12 +115,16 @@ public class BodyExtractorsTests {
|
||||
request.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
request.setBody(body);
|
||||
|
||||
StrategiesSupplier strategies = StrategiesSupplier.empty().build();
|
||||
BodyExtractor.Context emptyContext = new BodyExtractor.Context() {
|
||||
@Override
|
||||
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
|
||||
return () -> Collections.<HttpMessageReader<?>>emptySet().stream();
|
||||
}
|
||||
};
|
||||
|
||||
Flux<String> result = extractor.extract(request, strategies);
|
||||
Flux<String> result = extractor.extract(request, emptyContext);
|
||||
TestSubscriber.subscribe(result)
|
||||
.assertError(UnsupportedMediaTypeStatusException.class);
|
||||
|
||||
.assertError(UnsupportedMediaTypeException.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,20 +14,30 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function;
|
||||
package org.springframework.http.codec;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.codec.ByteBufferEncoder;
|
||||
import org.springframework.core.codec.CharSequenceEncoder;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.http.ReactiveHttpOutputMessage;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.tests.TestSubscriber;
|
||||
|
||||
@@ -40,15 +50,35 @@ import static org.junit.Assert.assertEquals;
|
||||
*/
|
||||
public class BodyInsertersTests {
|
||||
|
||||
private BodyInserter.Context context;
|
||||
|
||||
@Before
|
||||
public void createContext() {
|
||||
final List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
|
||||
messageWriters.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
|
||||
messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
|
||||
messageWriters.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
|
||||
messageWriters.add(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder()));
|
||||
|
||||
this.context = new BodyInserter.Context() {
|
||||
@Override
|
||||
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
|
||||
return messageWriters::stream;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void ofObject() throws Exception {
|
||||
String body = "foo";
|
||||
BodyInserter<String> inserter = BodyInserters.fromObject(body);
|
||||
BodyInserter<String, ReactiveHttpOutputMessage> inserter = BodyInserters.fromObject(body);
|
||||
|
||||
assertEquals(body, inserter.t());
|
||||
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
Mono<Void> result = inserter.insert(response, StrategiesSupplier.builder().build());
|
||||
Mono<Void> result = inserter.insert(response, this.context);
|
||||
TestSubscriber.subscribe(result)
|
||||
.assertComplete();
|
||||
|
||||
@@ -62,12 +92,12 @@ public class BodyInsertersTests {
|
||||
@Test
|
||||
public void ofPublisher() throws Exception {
|
||||
Flux<String> body = Flux.just("foo");
|
||||
BodyInserter<Flux<String>> inserter = BodyInserters.fromPublisher(body, String.class);
|
||||
BodyInserter<Flux<String>, ReactiveHttpOutputMessage> inserter = BodyInserters.fromPublisher(body, String.class);
|
||||
|
||||
assertEquals(body, inserter.t());
|
||||
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
Mono<Void> result = inserter.insert(response, StrategiesSupplier.builder().build());
|
||||
Mono<Void> result = inserter.insert(response, this.context);
|
||||
TestSubscriber.subscribe(result)
|
||||
.assertComplete();
|
||||
|
||||
@@ -81,12 +111,12 @@ public class BodyInsertersTests {
|
||||
@Test
|
||||
public void ofResource() throws Exception {
|
||||
Resource body = new ClassPathResource("response.txt", getClass());
|
||||
BodyInserter<Resource> inserter = BodyInserters.fromResource(body);
|
||||
BodyInserter<Resource, ReactiveHttpOutputMessage> inserter = BodyInserters.fromResource(body);
|
||||
|
||||
assertEquals(body, inserter.t());
|
||||
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
Mono<Void> result = inserter.insert(response, StrategiesSupplier.builder().build());
|
||||
Mono<Void> result = inserter.insert(response, this.context);
|
||||
TestSubscriber.subscribe(result)
|
||||
.assertComplete();
|
||||
|
||||
@@ -105,13 +135,13 @@ public class BodyInsertersTests {
|
||||
public void ofServerSentEventFlux() throws Exception {
|
||||
ServerSentEvent<String> event = ServerSentEvent.builder("foo").build();
|
||||
Flux<ServerSentEvent<String>> body = Flux.just(event);
|
||||
BodyInserter<Flux<ServerSentEvent<String>>> inserter =
|
||||
BodyInserter<Flux<ServerSentEvent<String>>, ServerHttpResponse> inserter =
|
||||
BodyInserters.fromServerSentEvents(body);
|
||||
|
||||
assertEquals(body, inserter.t());
|
||||
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
Mono<Void> result = inserter.insert(response, StrategiesSupplier.builder().build());
|
||||
Mono<Void> result = inserter.insert(response, this.context);
|
||||
TestSubscriber.subscribe(result)
|
||||
.assertComplete();
|
||||
|
||||
@@ -120,13 +150,13 @@ public class BodyInsertersTests {
|
||||
@Test
|
||||
public void ofServerSentEventClass() throws Exception {
|
||||
Flux<String> body = Flux.just("foo");
|
||||
BodyInserter<Flux<String>> inserter =
|
||||
BodyInserter<Flux<String>, ServerHttpResponse> inserter =
|
||||
BodyInserters.fromServerSentEvents(body, String.class);
|
||||
|
||||
assertEquals(body, inserter.t());
|
||||
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
Mono<Void> result = inserter.insert(response, StrategiesSupplier.builder().build());
|
||||
Mono<Void> result = inserter.insert(response, this.context);
|
||||
TestSubscriber.subscribe(result)
|
||||
.assertComplete();
|
||||
|
||||
Reference in New Issue
Block a user