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:
Arjen Poutsma
2016-12-07 09:34:57 +01:00
parent 4021d239ab
commit 582e625fcf
27 changed files with 570 additions and 525 deletions

View File

@@ -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

View File

@@ -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.
*/

View File

@@ -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();
}
}
}

View File

@@ -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.
*/

View File

@@ -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;