Allow HandlerFunction to return Mono<ServerResponse>
This commit makes it possible for handler functions to return asynchronous status codes and headers, by making HandlerFunction.handle return a Mono<ServerResponse> instead of a ServerResponse. As a consequence, all other types that deal with HandlerFunctions (RouterFunction, HandlerFilterFunction, etc.) had to change as well. However, when combining the above change with method references (a very typical use case), resulting signatures would have been something like: ``` public Mono<ServerResponse<Mono<Person>>> getPerson(ServerRequest request) ``` which was too ugly to consider, especially the two uses of Mono. It was considered to merge ServerResponse with the last Mono, essentialy making ServerResponse always contain a Publisher, but this had unfortunate consequences in view rendering. It was therefore decided to drop the parameterization of ServerResponse, as the only usage of the extra type information was to manipulate the response objects in a filter. Even before the above change this was suggested; it just made the change even more necessary. As a consequence, `BodyInserter` could be turned into a real `FunctionalInterface`, which resulted in changes in ClientRequest. We did, however, make HandlerFunction.handle return a `Mono<? extends ServerResponse>`, adding little complexity, but allowing for future `ServerResponse` subtypes that do expose type information, if it's needed. For instance, a RenderingResponse could expose the view name and model. Issue: SPR-14870
This commit is contained in:
@@ -33,7 +33,7 @@ import org.springframework.http.ReactiveHttpInputMessage;
|
||||
public interface BodyExtractor<T, M extends ReactiveHttpInputMessage> {
|
||||
|
||||
/**
|
||||
* Extract from the given request.
|
||||
* Extract from the given input message.
|
||||
* @param inputMessage request to extract from
|
||||
* @param context the configuration to use
|
||||
* @return the extracted data
|
||||
|
||||
@@ -16,14 +16,12 @@
|
||||
|
||||
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.ReactiveHttpOutputMessage;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A combination of functions that can populate a {@link ReactiveHttpOutputMessage} body.
|
||||
@@ -32,39 +30,17 @@ import org.springframework.util.Assert;
|
||||
* @since 5.0
|
||||
* @see BodyInserters
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BodyInserter<T, M extends ReactiveHttpOutputMessage> {
|
||||
|
||||
/**
|
||||
* Insert into the given response.
|
||||
* Insert into the given output message.
|
||||
* @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(M outputMessage, Context context);
|
||||
|
||||
/**
|
||||
* Return the type contained in the body.
|
||||
* @return the type contained in the body
|
||||
*/
|
||||
T t();
|
||||
|
||||
/**
|
||||
* Return a new {@code BodyInserter} described by the given writer and supplier functions.
|
||||
* @param writer the writer function for the new inserter
|
||||
* @param supplier the supplier function for the new inserter
|
||||
* @param <T> the type supplied and written by the inserter
|
||||
* @return the new {@code BodyInserter}
|
||||
*/
|
||||
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, M>(writer, supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the context used during the insertion.
|
||||
*/
|
||||
|
||||
@@ -18,7 +18,6 @@ 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;
|
||||
@@ -48,14 +47,17 @@ public abstract class BodyInserters {
|
||||
private static final ResolvableType SERVER_SIDE_EVENT_TYPE =
|
||||
ResolvableType.forClass(ServerSentEvent.class);
|
||||
|
||||
private static final BodyInserter<Void, ReactiveHttpOutputMessage> EMPTY =
|
||||
(response, context) -> response.setComplete();
|
||||
|
||||
|
||||
/**
|
||||
* Return an empty {@code BodyInserter} that writes nothing.
|
||||
* @return an empty {@code BodyInserter}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> BodyInserter<T, ReactiveHttpOutputMessage> empty() {
|
||||
return BodyInserter.of(
|
||||
(response, context) -> response.setComplete(),
|
||||
() -> null);
|
||||
return (BodyInserter<T, ReactiveHttpOutputMessage>)EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,9 +67,7 @@ public abstract class BodyInserters {
|
||||
*/
|
||||
public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromObject(T body) {
|
||||
Assert.notNull(body, "'body' must not be null");
|
||||
return BodyInserter.of(
|
||||
writeFunctionFor(Mono.just(body), ResolvableType.forInstance(body)),
|
||||
() -> body);
|
||||
return bodyInserterFor(Mono.just(body), ResolvableType.forInstance(body));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,18 +75,15 @@ 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 <P> the type of the {@code Publisher}
|
||||
* @return a {@code BodyInserter} that writes a {@code Publisher}
|
||||
*/
|
||||
public static <S extends Publisher<T>, T> BodyInserter<S, ReactiveHttpOutputMessage> fromPublisher(S publisher,
|
||||
public static <T, P extends Publisher<T>> BodyInserter<P, ReactiveHttpOutputMessage> fromPublisher(P publisher,
|
||||
Class<T> elementClass) {
|
||||
|
||||
Assert.notNull(publisher, "'publisher' must not be null");
|
||||
Assert.notNull(elementClass, "'elementClass' must not be null");
|
||||
return BodyInserter.of(
|
||||
writeFunctionFor(publisher, ResolvableType.forClass(elementClass)),
|
||||
() -> publisher
|
||||
);
|
||||
return bodyInserterFor(publisher, ResolvableType.forClass(elementClass));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,18 +91,15 @@ 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 <P> the type of the {@code Publisher}
|
||||
* @return a {@code BodyInserter} that writes a {@code Publisher}
|
||||
*/
|
||||
public static <S extends Publisher<T>, T> BodyInserter<S, ReactiveHttpOutputMessage> fromPublisher(S publisher,
|
||||
public static <T, P extends Publisher<T>> BodyInserter<P, ReactiveHttpOutputMessage> fromPublisher(P publisher,
|
||||
ResolvableType elementType) {
|
||||
|
||||
Assert.notNull(publisher, "'publisher' must not be null");
|
||||
Assert.notNull(elementType, "'elementType' must not be null");
|
||||
return BodyInserter.of(
|
||||
writeFunctionFor(publisher, elementType),
|
||||
() -> publisher
|
||||
);
|
||||
return bodyInserterFor(publisher, elementType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,14 +113,11 @@ public abstract class BodyInserters {
|
||||
*/
|
||||
public static <T extends Resource> BodyInserter<T, ReactiveHttpOutputMessage> fromResource(T resource) {
|
||||
Assert.notNull(resource, "'resource' must not be null");
|
||||
return BodyInserter.of(
|
||||
(response, context) -> {
|
||||
return (response, context) -> {
|
||||
HttpMessageWriter<Resource> messageWriter = resourceHttpMessageWriter(context);
|
||||
return messageWriter.write(Mono.just(resource), RESOURCE_TYPE, null,
|
||||
response, Collections.emptyMap());
|
||||
},
|
||||
() -> resource
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
private static HttpMessageWriter<Resource> resourceHttpMessageWriter(BodyInserter.Context context) {
|
||||
@@ -149,14 +140,11 @@ public abstract class BodyInserters {
|
||||
S eventsPublisher) {
|
||||
|
||||
Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null");
|
||||
return BodyInserter.of(
|
||||
(response, context) -> {
|
||||
return (response, context) -> {
|
||||
HttpMessageWriter<ServerSentEvent<T>> messageWriter = sseMessageWriter(context);
|
||||
return messageWriter.write(eventsPublisher, SERVER_SIDE_EVENT_TYPE,
|
||||
MediaType.TEXT_EVENT_STREAM, response, Collections.emptyMap());
|
||||
},
|
||||
() -> eventsPublisher
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,15 +180,12 @@ public abstract class BodyInserters {
|
||||
|
||||
Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null");
|
||||
Assert.notNull(eventType, "'eventType' must not be null");
|
||||
return BodyInserter.of(
|
||||
(outputMessage, context) -> {
|
||||
return (outputMessage, context) -> {
|
||||
HttpMessageWriter<T> messageWriter = sseMessageWriter(context);
|
||||
return messageWriter.write(eventsPublisher, eventType,
|
||||
MediaType.TEXT_EVENT_STREAM, outputMessage, Collections.emptyMap());
|
||||
|
||||
},
|
||||
() -> eventsPublisher
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,10 +199,7 @@ public abstract class BodyInserters {
|
||||
public static <T extends Publisher<DataBuffer>> BodyInserter<T, ReactiveHttpOutputMessage> fromDataBuffers(T publisher) {
|
||||
Assert.notNull(publisher, "'publisher' must not be null");
|
||||
|
||||
return BodyInserter.of(
|
||||
(outputMessage, context) -> outputMessage.writeWith(publisher),
|
||||
() -> publisher
|
||||
);
|
||||
return (outputMessage, context) -> outputMessage.writeWith(publisher);
|
||||
}
|
||||
|
||||
private static <T> HttpMessageWriter<T> sseMessageWriter(BodyInserter.Context context) {
|
||||
@@ -231,8 +213,7 @@ public abstract class BodyInserters {
|
||||
MediaType.TEXT_EVENT_STREAM_VALUE));
|
||||
}
|
||||
|
||||
private static <T, M extends ReactiveHttpOutputMessage> BiFunction<M, BodyInserter.Context, Mono<Void>>
|
||||
writeFunctionFor(Publisher<T> body, ResolvableType bodyType) {
|
||||
private static <T, P extends Publisher<?>, M extends ReactiveHttpOutputMessage> BodyInserter<T, M> bodyInserterFor(P body, ResolvableType bodyType) {
|
||||
|
||||
return (m, context) -> {
|
||||
|
||||
@@ -261,31 +242,5 @@ public abstract class BodyInserters {
|
||||
return (HttpMessageWriter<T>) messageWriter;
|
||||
}
|
||||
|
||||
static class DefaultBodyInserter<T, M extends ReactiveHttpOutputMessage>
|
||||
implements BodyInserter<T, M> {
|
||||
|
||||
private final BiFunction<M, Context, Mono<Void>> writer;
|
||||
|
||||
private final Supplier<T> supplier;
|
||||
|
||||
public DefaultBodyInserter(
|
||||
BiFunction<M, Context, Mono<Void>> writer,
|
||||
Supplier<T> supplier) {
|
||||
this.writer = writer;
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> insert(M outputMessage, Context context) {
|
||||
return this.writer.apply(outputMessage, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T t() {
|
||||
return this.supplier.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -67,11 +67,6 @@ public interface ClientRequest<T> {
|
||||
*/
|
||||
MultiValueMap<String, String> cookies();
|
||||
|
||||
/**
|
||||
* Return the body of this request.
|
||||
*/
|
||||
T body();
|
||||
|
||||
/**
|
||||
* Return the body inserter of this request.
|
||||
*/
|
||||
|
||||
@@ -121,9 +121,7 @@ class DefaultClientRequestBuilder implements ClientRequest.BodyBuilder {
|
||||
|
||||
@Override
|
||||
public ClientRequest<Void> build() {
|
||||
return body(BodyInserter.of(
|
||||
(response, configuration) -> response.setComplete(),
|
||||
() -> null));
|
||||
return body(BodyInserters.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -192,11 +190,6 @@ class DefaultClientRequestBuilder implements ClientRequest.BodyBuilder {
|
||||
return this.cookies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T body() {
|
||||
return this.inserter.t();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BodyInserter<T, ? super ClientHttpRequest> inserter() {
|
||||
return this.inserter;
|
||||
|
||||
Reference in New Issue
Block a user