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

@@ -17,44 +17,26 @@
package org.springframework.web.reactive.function;
import java.net.URI;
import java.nio.ByteBuffer;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.Set;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.CacheControl;
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;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.reactive.result.view.View;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -65,130 +47,203 @@ public class DefaultServerResponseBuilderTests {
@Test
public void from() throws Exception {
ServerResponse<Void> other = ServerResponse.ok().header("foo", "bar").build();
ServerResponse<Void> result = ServerResponse.from(other).build();
assertEquals(HttpStatus.OK, result.statusCode());
assertEquals("bar", result.headers().getFirst("foo"));
ServerResponse other = ServerResponse.ok().header("foo", "bar").build().block();
Mono<ServerResponse> result = ServerResponse.from(other).build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.OK.equals(response.statusCode()) &&
"bar".equals(response.headers().getFirst("foo")))
.expectComplete()
.verify();
}
@Test
public void status() throws Exception {
ServerResponse<Void> result = ServerResponse.status(HttpStatus.CREATED).build();
assertEquals(HttpStatus.CREATED, result.statusCode());
Mono<ServerResponse> result = ServerResponse.status(HttpStatus.CREATED).build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.CREATED.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void ok() throws Exception {
ServerResponse<Void> result = ServerResponse.ok().build();
assertEquals(HttpStatus.OK, result.statusCode());
Mono<ServerResponse> result = ServerResponse.ok().build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.OK.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void created() throws Exception {
URI location = URI.create("http://example.com");
ServerResponse<Void> result = ServerResponse.created(location).build();
assertEquals(HttpStatus.CREATED, result.statusCode());
assertEquals(location, result.headers().getLocation());
Mono<ServerResponse> result = ServerResponse.created(location).build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.CREATED.equals(response.statusCode()) &&
location.equals(response.headers().getLocation()))
.expectComplete()
.verify();
}
@Test
public void accepted() throws Exception {
ServerResponse<Void> result = ServerResponse.accepted().build();
assertEquals(HttpStatus.ACCEPTED, result.statusCode());
Mono<ServerResponse> result = ServerResponse.accepted().build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.ACCEPTED.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void noContent() throws Exception {
ServerResponse<Void> result = ServerResponse.noContent().build();
assertEquals(HttpStatus.NO_CONTENT, result.statusCode());
Mono<ServerResponse> result = ServerResponse.noContent().build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.NO_CONTENT.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void badRequest() throws Exception {
ServerResponse<Void> result = ServerResponse.badRequest().build();
assertEquals(HttpStatus.BAD_REQUEST, result.statusCode());
Mono<ServerResponse> result = ServerResponse.badRequest().build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.BAD_REQUEST.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void notFound() throws Exception {
ServerResponse<Void> result = ServerResponse.notFound().build();
assertEquals(HttpStatus.NOT_FOUND, result.statusCode());
Mono<ServerResponse> result = ServerResponse.notFound().build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.NOT_FOUND.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void unprocessableEntity() throws Exception {
ServerResponse<Void> result = ServerResponse.unprocessableEntity().build();
assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, result.statusCode());
Mono<ServerResponse> result = ServerResponse.unprocessableEntity().build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.UNPROCESSABLE_ENTITY.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void allow() throws Exception {
ServerResponse<Void> result = ServerResponse.ok().allow(HttpMethod.GET).build();
assertEquals(Collections.singleton(HttpMethod.GET), result.headers().getAllow());
Mono<ServerResponse> result = ServerResponse.ok().allow(HttpMethod.GET).build();
Set<HttpMethod> expected = EnumSet.of(HttpMethod.GET);
StepVerifier.create(result)
.expectNextMatches(response -> expected.equals(response.headers().getAllow()))
.expectComplete()
.verify();
}
@Test
public void contentLength() throws Exception {
ServerResponse<Void> result = ServerResponse.ok().contentLength(42).build();
assertEquals(42, result.headers().getContentLength());
Mono<ServerResponse> result = ServerResponse.ok().contentLength(42).build();
StepVerifier.create(result)
.expectNextMatches(response -> Long.valueOf(42).equals(response.headers().getContentLength()))
.expectComplete()
.verify();
}
@Test
public void contentType() throws Exception {
ServerResponse<Void> result = ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).build();
assertEquals(MediaType.APPLICATION_JSON, result.headers().getContentType());
Mono<ServerResponse>
result = ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).build();
StepVerifier.create(result)
.expectNextMatches(response -> MediaType.APPLICATION_JSON.equals(response.headers().getContentType()))
.expectComplete()
.verify();
}
@Test
public void eTag() throws Exception {
ServerResponse<Void> result = ServerResponse.ok().eTag("foo").build();
assertEquals("\"foo\"", result.headers().getETag());
Mono<ServerResponse> result = ServerResponse.ok().eTag("foo").build();
StepVerifier.create(result)
.expectNextMatches(response -> "\"foo\"".equals(response.headers().getETag()))
.expectComplete()
.verify();
}
@Test
public void lastModified() throws Exception {
ZonedDateTime now = ZonedDateTime.now();
ServerResponse<Void> result = ServerResponse.ok().lastModified(now).build();
assertEquals(now.toInstant().toEpochMilli()/1000, result.headers().getLastModified()/1000);
Mono<ServerResponse> result = ServerResponse.ok().lastModified(now).build();
Long expected = now.toInstant().toEpochMilli() / 1000;
StepVerifier.create(result)
.expectNextMatches(response -> expected.equals(response.headers().getLastModified() / 1000))
.expectComplete()
.verify();
}
@Test
public void cacheControlTag() throws Exception {
ServerResponse<Void> result = ServerResponse.ok().cacheControl(CacheControl.noCache()).build();
assertEquals("no-cache", result.headers().getCacheControl());
Mono<ServerResponse>
result = ServerResponse.ok().cacheControl(CacheControl.noCache()).build();
StepVerifier.create(result)
.expectNextMatches(response -> "no-cache".equals(response.headers().getCacheControl()))
.expectComplete()
.verify();
}
@Test
public void varyBy() throws Exception {
ServerResponse<Void> result = ServerResponse.ok().varyBy("foo").build();
assertEquals(Collections.singletonList("foo"), result.headers().getVary());
Mono<ServerResponse> result = ServerResponse.ok().varyBy("foo").build();
List<String> expected = Collections.singletonList("foo");
StepVerifier.create(result)
.expectNextMatches(response -> expected.equals(response.headers().getVary()))
.expectComplete()
.verify();
}
@Test
public void statusCode() throws Exception {
HttpStatus statusCode = HttpStatus.ACCEPTED;
ServerResponse<Void> result = ServerResponse.status(statusCode).build();
assertSame(statusCode, result.statusCode());
Mono<ServerResponse> result = ServerResponse.status(statusCode).build();
StepVerifier.create(result)
.expectNextMatches(response -> statusCode.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void headers() throws Exception {
HttpHeaders headers = new HttpHeaders();
ServerResponse<Void> result = ServerResponse.ok().headers(headers).build();
assertEquals(headers, result.headers());
Mono<ServerResponse> result = ServerResponse.ok().headers(headers).build();
StepVerifier.create(result)
.expectNextMatches(response -> headers.equals(response.headers()))
.expectComplete()
.verify();
}
@Test
public void build() throws Exception {
ServerResponse<Void> result = ServerResponse.status(HttpStatus.CREATED).header("MyKey", "MyValue").build();
Mono<ServerResponse>
result = ServerResponse.status(HttpStatus.CREATED).header("MyKey", "MyValue").build();
ServerWebExchange exchange = mock(ServerWebExchange.class);
MockServerHttpResponse response = new MockServerHttpResponse();
when(exchange.getResponse()).thenReturn(response);
HandlerStrategies strategies = mock(HandlerStrategies.class);
result.writeTo(exchange, strategies).block();
assertEquals(201, response.getStatusCode().value());
result.then(res -> res.writeTo(exchange, strategies)).block();
assertEquals(HttpStatus.CREATED, response.getStatusCode());
assertEquals("MyValue", response.getHeaders().getFirst("MyKey"));
assertNull(response.getBody());
@@ -197,21 +252,25 @@ public class DefaultServerResponseBuilderTests {
@Test
public void buildVoidPublisher() throws Exception {
Mono<Void> mono = Mono.empty();
ServerResponse<Mono<Void>> result = ServerResponse.ok().build(mono);
Mono<ServerResponse> result = ServerResponse.ok().build(mono);
ServerWebExchange exchange = mock(ServerWebExchange.class);
MockServerHttpResponse response = new MockServerHttpResponse();
when(exchange.getResponse()).thenReturn(response);
HandlerStrategies strategies = mock(HandlerStrategies.class);
result.writeTo(exchange, strategies).block();
result.then(res -> res.writeTo(exchange, strategies)).block();
assertNull(response.getBody());
}
/*
TODO: enable when ServerEntityResponse is reintroduced
@Test
public void bodyInserter() throws Exception {
String body = "foo";
Supplier<String> supplier = () -> body;
Publisher<String> publisher = Mono.just(body);
BiFunction<ServerHttpResponse, BodyInserter.Context, Mono<Void>> writer =
(response, strategies) -> {
byte[] bodyBytes = body.getBytes(UTF_8);
@@ -221,14 +280,13 @@ public class DefaultServerResponseBuilderTests {
return response.writeWith(Mono.just(buffer));
};
ServerResponse<String> result = ServerResponse.ok().body(BodyInserter.of(writer, supplier));
assertEquals(body, result.body());
Mono<ServerResponse> result = ServerResponse.ok().body(BodyInserter.of(writer, publisher));
MockServerHttpRequest request =
new MockServerHttpRequest(HttpMethod.GET, "http://localhost");
MockServerHttpResponse response = new MockServerHttpResponse();
MockServerHttpResponse mockResponse = new MockServerHttpResponse();
ServerWebExchange exchange =
new DefaultServerWebExchange(request, response, new MockWebSessionManager());
new DefaultServerWebExchange(request, mockResponse, new MockWebSessionManager());
List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
messageWriters.add(new EncoderHttpMessageWriter<CharSequence>(new CharSequenceEncoder()));
@@ -236,21 +294,32 @@ public class DefaultServerResponseBuilderTests {
HandlerStrategies strategies = mock(HandlerStrategies.class);
when(strategies.messageWriters()).thenReturn(messageWriters::stream);
result.writeTo(exchange, strategies).block();
assertNotNull(response.getBody());
}
StepVerifier.create(result)
.consumeNextWith(response -> {
StepVerifier.create(response.body())
.expectNext(body)
.expectComplete()
.verify();
response.writeTo(exchange, strategies);
})
.expectComplete()
.verify();
assertNotNull(mockResponse.getBody());
}
*/
/*
TODO: enable when ServerEntityResponse is reintroduced
@Test
public void render() throws Exception {
Map<String, Object> model = Collections.singletonMap("foo", "bar");
ServerResponse<Rendering> result = ServerResponse.ok().render("view", model);
Mono<ServerResponse> result = ServerResponse.ok().render("view", model);
assertEquals("view", result.body().name());
assertEquals(model, result.body().model());
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, URI.create("http://localhost"));
MockServerHttpResponse response = new MockServerHttpResponse();
ServerWebExchange exchange = new DefaultServerWebExchange(request, response, new MockWebSessionManager());
MockServerHttpResponse mockResponse = new MockServerHttpResponse();
ServerWebExchange exchange = new DefaultServerWebExchange(request, mockResponse, new MockWebSessionManager());
ViewResolver viewResolver = mock(ViewResolver.class);
View view = mock(View.class);
when(viewResolver.resolveViewName("view", Locale.ENGLISH)).thenReturn(Mono.just(view));
@@ -262,17 +331,37 @@ public class DefaultServerResponseBuilderTests {
HandlerStrategies mockConfig = mock(HandlerStrategies.class);
when(mockConfig.viewResolvers()).thenReturn(viewResolvers::stream);
result.writeTo(exchange, mockConfig).block();
StepVerifier.create(result)
.consumeNextWith(response -> {
StepVerifier.create(response.body())
.expectNextMatches(rendering -> "view".equals(rendering.name())
&& model.equals(rendering.model()))
.expectComplete()
.verify();
})
.expectComplete()
.verify();
}
*/
/*
TODO: enable when ServerEntityResponse is reintroduced
@Test
public void renderObjectArray() throws Exception {
ServerResponse<Rendering> result =
Mono<ServerResponse> result =
ServerResponse.ok().render("name", this, Collections.emptyList(), "foo");
Map<String, Object> model = result.body().model();
assertEquals(2, model.size());
assertEquals(this, model.get("defaultServerResponseBuilderTests"));
assertEquals("foo", model.get("string"));
Flux<Rendering> map = result.flatMap(ServerResponse::body);
Map<String, Object> expected = new HashMap<>(2);
expected.put("defaultServerResponseBuilderTests", this);
expected.put("string", "foo");
StepVerifier.create(map)
.expectNextMatches(rendering -> expected.equals(rendering.model()))
.expectComplete()
.verify();
}
*/
}

View File

@@ -16,14 +16,12 @@
package org.springframework.web.reactive.function;
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 org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -50,6 +48,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.fromObject;
import static org.springframework.http.codec.BodyInserters.fromPublisher;
import static org.springframework.web.reactive.function.RouterFunctions.route;
@@ -84,7 +83,7 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr
@Test
public void mono() throws Exception {
ResponseEntity<Person> result =
restTemplate.getForEntity("http://localhost:" + port + "/mono", Person.class);
this.restTemplate.getForEntity("http://localhost:" + this.port + "/mono", Person.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("John", result.getBody().getName());
@@ -94,7 +93,8 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr
public void flux() throws Exception {
ParameterizedTypeReference<List<Person>> reference = new ParameterizedTypeReference<List<Person>>() {};
ResponseEntity<List<Person>> result =
restTemplate.exchange("http://localhost:" + port + "/flux", HttpMethod.GET, null, reference);
this.restTemplate
.exchange("http://localhost:" + this.port + "/flux", HttpMethod.GET, null, reference);
assertEquals(HttpStatus.OK, result.getStatusCode());
List<Person> body = result.getBody();
@@ -134,7 +134,7 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr
@Override
public Supplier<Stream<ViewResolver>> viewResolvers() {
return () -> Collections.<ViewResolver>emptySet().stream();
return Stream::empty;
}
});
}
@@ -154,18 +154,22 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr
private static class PersonHandler {
public ServerResponse<Publisher<Person>> mono(ServerRequest request) {
public Mono<ServerResponse> mono(ServerRequest request) {
Person person = new Person("John");
return ServerResponse.ok().body(fromPublisher(Mono.just(person), Person.class));
return ServerResponse.ok().body(fromObject(person));
}
public ServerResponse<Publisher<Person>> flux(ServerRequest request) {
public Mono<ServerResponse> flux(ServerRequest request) {
Person person1 = new Person("John");
Person person2 = new Person("Jane");
return ServerResponse.ok().body(
fromPublisher(Flux.just(person1, person2), Person.class));
}
public Mono<ServerResponse> view() {
return ServerResponse.ok().render("foo", "bar");
}
}
private static class Person {
@@ -181,7 +185,7 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr
}
public String getName() {
return name;
return this.name;
}
public void setName(String name) {
@@ -209,7 +213,7 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
"name='" + this.name + '\'' +
'}';
}
}

View File

@@ -45,7 +45,7 @@ import org.springframework.util.MultiValueMap;
/**
* @author Arjen Poutsma
*/
public class MockServerRequest<T> implements ServerRequest {
public class MockServerRequest implements ServerRequest {
private final HttpMethod method;
@@ -53,7 +53,7 @@ public class MockServerRequest<T> implements ServerRequest {
private final MockHeaders headers;
private final T body;
private final Object body;
private final Map<String, Object> attributes;
@@ -62,7 +62,7 @@ public class MockServerRequest<T> implements ServerRequest {
private final Map<String, String> pathVariables;
private MockServerRequest(HttpMethod method, URI uri,
MockHeaders headers, T body, Map<String, Object> attributes,
MockHeaders headers, Object body, Map<String, Object> attributes,
MultiValueMap<String, String> queryParams,
Map<String, String> pathVariables) {
this.method = method;
@@ -74,8 +74,8 @@ public class MockServerRequest<T> implements ServerRequest {
this.pathVariables = pathVariables;
}
public static <T> Builder<T> builder() {
return new BuilderImpl<T>();
public static Builder builder() {
return new BuilderImpl();
}
@Override
@@ -127,35 +127,35 @@ public class MockServerRequest<T> implements ServerRequest {
return Collections.unmodifiableMap(this.pathVariables);
}
public interface Builder<T> {
public interface Builder {
Builder<T> method(HttpMethod method);
Builder method(HttpMethod method);
Builder<T> uri(URI uri);
Builder uri(URI uri);
Builder<T> header(String key, String value);
Builder header(String key, String value);
Builder<T> headers(HttpHeaders headers);
Builder headers(HttpHeaders headers);
Builder<T> attribute(String name, Object value);
Builder attribute(String name, Object value);
Builder<T> attributes(Map<String, Object> attributes);
Builder attributes(Map<String, Object> attributes);
Builder<T> queryParam(String key, String value);
Builder queryParam(String key, String value);
Builder<T> queryParams(MultiValueMap<String, String> queryParams);
Builder queryParams(MultiValueMap<String, String> queryParams);
Builder<T> pathVariable(String key, String value);
Builder pathVariable(String key, String value);
Builder<T> pathVariables(Map<String, String> pathVariables);
Builder pathVariables(Map<String, String> pathVariables);
MockServerRequest<T> body(T body);
MockServerRequest body(Object body);
MockServerRequest<Void> build();
MockServerRequest build();
}
private static class BuilderImpl<T> implements Builder<T> {
private static class BuilderImpl implements Builder {
private HttpMethod method = HttpMethod.GET;
@@ -163,7 +163,7 @@ public class MockServerRequest<T> implements ServerRequest {
private MockHeaders headers = new MockHeaders(new HttpHeaders());
private T body;
private Object body;
private Map<String, Object> attributes = new LinkedHashMap<>();
@@ -172,21 +172,21 @@ public class MockServerRequest<T> implements ServerRequest {
private Map<String, String> pathVariables = new LinkedHashMap<>();
@Override
public Builder<T> method(HttpMethod method) {
public Builder method(HttpMethod method) {
Assert.notNull(method, "'method' must not be null");
this.method = method;
return this;
}
@Override
public Builder<T> uri(URI uri) {
public Builder uri(URI uri) {
Assert.notNull(uri, "'uri' must not be null");
this.uri = uri;
return this;
}
@Override
public Builder<T> header(String key, String value) {
public Builder header(String key, String value) {
Assert.notNull(key, "'key' must not be null");
Assert.notNull(value, "'value' must not be null");
this.headers.header(key, value);
@@ -194,14 +194,14 @@ public class MockServerRequest<T> implements ServerRequest {
}
@Override
public Builder<T> headers(HttpHeaders headers) {
public Builder headers(HttpHeaders headers) {
Assert.notNull(headers, "'headers' must not be null");
this.headers = new MockHeaders(headers);
return this;
}
@Override
public Builder<T> attribute(String name, Object value) {
public Builder attribute(String name, Object value) {
Assert.notNull(name, "'name' must not be null");
Assert.notNull(value, "'value' must not be null");
this.attributes.put(name, value);
@@ -209,14 +209,14 @@ public class MockServerRequest<T> implements ServerRequest {
}
@Override
public Builder<T> attributes(Map<String, Object> attributes) {
public Builder attributes(Map<String, Object> attributes) {
Assert.notNull(attributes, "'attributes' must not be null");
this.attributes = attributes;
return this;
}
@Override
public Builder<T> queryParam(String key, String value) {
public Builder queryParam(String key, String value) {
Assert.notNull(key, "'key' must not be null");
Assert.notNull(value, "'value' must not be null");
this.queryParams.add(key, value);
@@ -224,14 +224,14 @@ public class MockServerRequest<T> implements ServerRequest {
}
@Override
public Builder<T> queryParams(MultiValueMap<String, String> queryParams) {
public Builder queryParams(MultiValueMap<String, String> queryParams) {
Assert.notNull(queryParams, "'queryParams' must not be null");
this.queryParams = queryParams;
return this;
}
@Override
public Builder<T> pathVariable(String key, String value) {
public Builder pathVariable(String key, String value) {
Assert.notNull(key, "'key' must not be null");
Assert.notNull(value, "'value' must not be null");
this.pathVariables.put(key, value);
@@ -239,22 +239,22 @@ public class MockServerRequest<T> implements ServerRequest {
}
@Override
public Builder<T> pathVariables(Map<String, String> pathVariables) {
public Builder pathVariables(Map<String, String> pathVariables) {
Assert.notNull(pathVariables, "'pathVariables' must not be null");
this.pathVariables = pathVariables;
return this;
}
@Override
public MockServerRequest<T> body(T body) {
public MockServerRequest body(Object body) {
this.body = body;
return new MockServerRequest<T>(this.method, this.uri, this.headers, this.body,
return new MockServerRequest(this.method, this.uri, this.headers, this.body,
this.attributes, this.queryParams, this.pathVariables);
}
@Override
public MockServerRequest<Void> build() {
return new MockServerRequest<Void>(this.method, this.uri, this.headers, null,
public MockServerRequest build() {
return new MockServerRequest(this.method, this.uri, this.headers, null,
this.attributes, this.queryParams, this.pathVariables);
}

View File

@@ -16,18 +16,17 @@
package org.springframework.web.reactive.function;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Optional;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author Arjen Poutsma
*/
@@ -38,14 +37,23 @@ public class PathResourceLookupFunctionTests {
ClassPathResource location = new ClassPathResource("org/springframework/web/reactive/function/");
PathResourceLookupFunction function = new PathResourceLookupFunction("/resources/**", location);
MockServerRequest<Void> request = MockServerRequest.builder()
MockServerRequest request = MockServerRequest.builder()
.uri(new URI("http://localhost/resources/response.txt"))
.build();
Optional<Resource> result = function.apply(request);
assertTrue(result.isPresent());
Mono<Resource> result = function.apply(request);
ClassPathResource expected = new ClassPathResource("response.txt", getClass());
assertEquals(expected.getFile(), result.get().getFile());
File expected = new ClassPathResource("response.txt", getClass()).getFile();
StepVerifier.create(result)
.expectNextMatches(resource -> {
try {
return expected.equals(resource.getFile());
}
catch (IOException ex) {
return false;
}
})
.expectComplete()
.verify();
}
@Test
@@ -53,14 +61,22 @@ public class PathResourceLookupFunctionTests {
ClassPathResource location = new ClassPathResource("org/springframework/web/reactive/function/");
PathResourceLookupFunction function = new PathResourceLookupFunction("/resources/**", location);
MockServerRequest<Void> request = MockServerRequest.builder()
MockServerRequest request = MockServerRequest.builder()
.uri(new URI("http://localhost/resources/child/response.txt"))
.build();
Optional<Resource> result = function.apply(request);
assertTrue(result.isPresent());
ClassPathResource expected = new ClassPathResource("org/springframework/web/reactive/function/child/response.txt");
assertEquals(expected.getFile(), result.get().getFile());
Mono<Resource> result = function.apply(request);
File expected = new ClassPathResource("org/springframework/web/reactive/function/child/response.txt").getFile();
StepVerifier.create(result)
.expectNextMatches(resource -> {
try {
return expected.equals(resource.getFile());
}
catch (IOException ex) {
return false;
}
})
.expectComplete()
.verify();
}
@Test
@@ -68,11 +84,13 @@ public class PathResourceLookupFunctionTests {
ClassPathResource location = new ClassPathResource("org/springframework/web/reactive/function/");
PathResourceLookupFunction function = new PathResourceLookupFunction("/resources/**", location);
MockServerRequest<Void> request = MockServerRequest.builder()
MockServerRequest request = MockServerRequest.builder()
.uri(new URI("http://localhost/resources/foo"))
.build();
Optional<Resource> result = function.apply(request);
assertFalse(result.isPresent());
Mono<Resource> result = function.apply(request);
StepVerifier.create(result)
.expectComplete()
.verify();
}
}

View File

@@ -21,7 +21,6 @@ import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -97,17 +96,17 @@ public class PublisherHandlerFunctionIntegrationTests
private static class PersonHandler {
public ServerResponse<Publisher<Person>> mono(ServerRequest request) {
public Mono<ServerResponse> mono(ServerRequest request) {
Person person = new Person("John");
return ServerResponse.ok().body(fromPublisher(Mono.just(person), Person.class));
}
public ServerResponse<Publisher<Person>> postMono(ServerRequest request) {
public Mono<ServerResponse> postMono(ServerRequest request) {
Mono<Person> personMono = request.body(toMono(Person.class));
return ServerResponse.ok().body(fromPublisher(personMono, Person.class));
}
public ServerResponse<Publisher<Person>> flux(ServerRequest request) {
public Mono<ServerResponse> flux(ServerRequest request) {
Person person1 = new Person("John");
Person person2 = new Person("Jane");
return ServerResponse.ok().body(

View File

@@ -57,18 +57,24 @@ public class ResourceHandlerFunctionTests {
ServerRequest request = new DefaultServerRequest(exchange, HandlerStrategies.withDefaults());
ServerResponse<Resource> response = this.handlerFunction.handle(request);
assertEquals(HttpStatus.OK, response.statusCode());
assertEquals(this.resource, response.body());
Mono<ServerResponse> responseMono = this.handlerFunction.handle(request);
Mono<Void> result = response.writeTo(exchange, HandlerStrategies.withDefaults());
Mono<Void> result = responseMono.then(response -> {
assertEquals(HttpStatus.OK, response.statusCode());
/*
TODO: enable when ServerEntityResponse is reintroduced
StepVerifier.create(response.body())
.expectNext(this.resource)
.expectComplete()
.verify();
*/
return response.writeTo(exchange, HandlerStrategies.withDefaults());
});
StepVerifier.create(result)
.expectComplete()
.verify();
StepVerifier.create(result).expectComplete().verify();
byte[] expectedBytes = Files.readAllBytes(this.resource.getFile().toPath());
StepVerifier.create(mockResponse.getBody())
@@ -93,10 +99,12 @@ public class ResourceHandlerFunctionTests {
ServerRequest request = new DefaultServerRequest(exchange, HandlerStrategies.withDefaults());
ServerResponse<Resource> response = this.handlerFunction.handle(request);
assertEquals(HttpStatus.OK, response.statusCode());
Mono<ServerResponse> response = this.handlerFunction.handle(request);
Mono<Void> result = response.writeTo(exchange, HandlerStrategies.withDefaults());
Mono<Void> result = response.then(res -> {
assertEquals(HttpStatus.OK, res.statusCode());
return res.writeTo(exchange, HandlerStrategies.withDefaults());
});
StepVerifier.create(result)
.expectComplete()
@@ -121,14 +129,20 @@ public class ResourceHandlerFunctionTests {
ServerRequest request = new DefaultServerRequest(exchange, HandlerStrategies.withDefaults());
ServerResponse<Resource> response = this.handlerFunction.handle(request);
Mono<ServerResponse> responseMono = this.handlerFunction.handle(request);
Mono<Void> result = responseMono.then(response -> {
assertEquals(HttpStatus.OK, response.statusCode());
assertEquals(EnumSet.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS),
response.headers().getAllow());
/*
TODO: enable when ServerEntityResponse is reintroduced
StepVerifier.create(response.body())
.expectComplete()
.verify();
*/
return response.writeTo(exchange, HandlerStrategies.withDefaults());
});
assertEquals(HttpStatus.OK, response.statusCode());
assertEquals(EnumSet.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS),
response.headers().getAllow());
assertNull(response.body());
Mono<Void> result = response.writeTo(exchange, HandlerStrategies.withDefaults());
StepVerifier.create(result)
.expectComplete()

View File

@@ -16,13 +16,11 @@
package org.springframework.web.reactive.function;
import java.util.Optional;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.http.codec.BodyInserters.fromObject;
/**
@@ -33,69 +31,96 @@ public class RouterFunctionTests {
@Test
public void andSame() throws Exception {
HandlerFunction<Void> handlerFunction = request -> ServerResponse.ok().build();
RouterFunction<Void> routerFunction1 = request -> Optional.empty();
RouterFunction<Void> routerFunction2 = request -> Optional.of(handlerFunction);
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
RouterFunction<ServerResponse> routerFunction1 = request -> Mono.empty();
RouterFunction<ServerResponse> routerFunction2 = request -> Mono.just(handlerFunction);
RouterFunction<Void> result = routerFunction1.andSame(routerFunction2);
RouterFunction<ServerResponse> result = routerFunction1.andSame(routerFunction2);
assertNotNull(result);
MockServerRequest request = MockServerRequest.builder().build();
Optional<HandlerFunction<Void>> resultHandlerFunction = result.route(request);
assertTrue(resultHandlerFunction.isPresent());
assertEquals(handlerFunction, resultHandlerFunction.get());
Mono<HandlerFunction<ServerResponse>> resultHandlerFunction = result.route(request);
StepVerifier.create(resultHandlerFunction)
.expectNext(handlerFunction)
.expectComplete()
.verify();
}
@Test
public void and() throws Exception {
HandlerFunction<String> handlerFunction = request -> ServerResponse.ok().body(fromObject("42"));
RouterFunction<Void> routerFunction1 = request -> Optional.empty();
RouterFunction<String> routerFunction2 = request -> Optional.of(handlerFunction);
HandlerFunction<ServerResponse> handlerFunction =
request -> ServerResponse.ok().body(fromObject("42"));
RouterFunction<?> routerFunction1 = request -> Mono.empty();
RouterFunction<ServerResponse> routerFunction2 =
request -> Mono.just(handlerFunction);
RouterFunction<?> result = routerFunction1.and(routerFunction2);
assertNotNull(result);
MockServerRequest request = MockServerRequest.builder().build();
Optional<? extends HandlerFunction<?>> resultHandlerFunction = result.route(request);
assertTrue(resultHandlerFunction.isPresent());
assertEquals(handlerFunction, resultHandlerFunction.get());
Mono<? extends HandlerFunction<?>> resultHandlerFunction = result.route(request);
StepVerifier.create(resultHandlerFunction)
.expectNextMatches(o -> o.equals(handlerFunction))
.expectComplete()
.verify();
}
@Test
public void andRoute() throws Exception {
RouterFunction<Integer> routerFunction1 = request -> Optional.empty();
RouterFunction<?> routerFunction1 = request -> Mono.empty();
RequestPredicate requestPredicate = request -> true;
RouterFunction<?> result = routerFunction1.andRoute(requestPredicate, this::handlerMethod);
assertNotNull(result);
MockServerRequest request = MockServerRequest.builder().build();
Optional<? extends HandlerFunction<?>> resultHandlerFunction = result.route(request);
assertTrue(resultHandlerFunction.isPresent());
Mono<? extends HandlerFunction<?>> resultHandlerFunction = result.route(request);
StepVerifier.create(resultHandlerFunction)
.expectNextCount(1)
.expectComplete()
.verify();
}
private ServerResponse<String> handlerMethod(ServerRequest request) {
private Mono<ServerResponse> handlerMethod(ServerRequest request) {
return ServerResponse.ok().body(fromObject("42"));
}
/*
TODO: enable when ServerEntityResponse is reintroduced
@Test
public void filter() throws Exception {
HandlerFunction<String> handlerFunction = request -> ServerResponse.ok().body(fromObject("42"));
RouterFunction<String> routerFunction = request -> Optional.of(handlerFunction);
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().body(fromObject("42"));
RouterFunction<ServerResponse> routerFunction = request -> Mono.just(handlerFunction);
HandlerFilterFunction<String, Integer> filterFunction = (request, next) -> {
ServerResponse<String> response = next.handle(request);
int i = Integer.parseInt(response.body());
return ServerResponse.ok().body(fromObject(i));
};
HandlerFilterFunction<String, Integer> filterFunction =
(request, next) -> next.handle(request).then(
response -> {
Flux<Integer> body = Flux.from(response.body())
.map(Integer::parseInt);
return ServerResponse.ok().body(body, Integer.class);
});
RouterFunction<Integer> result = routerFunction.filter(filterFunction);
assertNotNull(result);
MockServerRequest request = MockServerRequest.builder().build();
Optional<? extends HandlerFunction<?>> resultHandlerFunction = result.route(request);
assertTrue(resultHandlerFunction.isPresent());
ServerResponse<?> resultResponse = resultHandlerFunction.get().handle(request);
assertEquals(42, resultResponse.body());
Mono<? extends ServerResponse<Integer>> responseMono =
result.route(request).then(hf -> hf.handle(request));
StepVerifier.create(responseMono)
.consumeNextWith(
serverResponse -> {
StepVerifier.create(serverResponse.body())
.expectNext(42)
.expectComplete()
.verify();
})
.expectComplete()
.verify();
}
*/
}

View File

@@ -16,11 +16,11 @@
package org.springframework.web.reactive.function;
import java.util.Collections;
import java.util.Optional;
import java.util.stream.Stream;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.HttpMethod;
import org.springframework.http.codec.HttpMessageReader;
@@ -31,8 +31,11 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Arjen Poutsma
@@ -42,87 +45,97 @@ public class RouterFunctionsTests {
@Test
public void routeMatch() throws Exception {
HandlerFunction<Void> handlerFunction = request -> ServerResponse.ok().build();
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
MockServerRequest request = MockServerRequest.builder().build();
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(true);
RouterFunction<Void> result = RouterFunctions.route(requestPredicate, handlerFunction);
RouterFunction<ServerResponse> result = RouterFunctions.route(requestPredicate, handlerFunction);
assertNotNull(result);
Optional<HandlerFunction<Void>> resultHandlerFunction = result.route(request);
assertTrue(resultHandlerFunction.isPresent());
assertEquals(handlerFunction, resultHandlerFunction.get());
Mono<HandlerFunction<ServerResponse>> resultHandlerFunction = result.route(request);
StepVerifier.create(resultHandlerFunction)
.expectNext(handlerFunction)
.expectComplete()
.verify();
}
@Test
public void routeNoMatch() throws Exception {
HandlerFunction<Void> handlerFunction = request -> ServerResponse.ok().build();
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
MockServerRequest request = MockServerRequest.builder().build();
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(false);
RouterFunction<Void> result = RouterFunctions.route(requestPredicate, handlerFunction);
RouterFunction<ServerResponse> result = RouterFunctions.route(requestPredicate, handlerFunction);
assertNotNull(result);
Optional<HandlerFunction<Void>> resultHandlerFunction = result.route(request);
assertFalse(resultHandlerFunction.isPresent());
Mono<HandlerFunction<ServerResponse>> resultHandlerFunction = result.route(request);
StepVerifier.create(resultHandlerFunction)
.expectComplete()
.verify();
}
@Test
public void subrouteMatch() throws Exception {
HandlerFunction<Void> handlerFunction = request -> ServerResponse.ok().build();
RouterFunction<Void> routerFunction = request -> Optional.of(handlerFunction);
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
RouterFunction<ServerResponse> routerFunction = request -> Mono.just(handlerFunction);
MockServerRequest request = MockServerRequest.builder().build();
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(true);
RouterFunction<Void> result = RouterFunctions.subroute(requestPredicate, routerFunction);
RouterFunction<ServerResponse> result = RouterFunctions.subroute(requestPredicate, routerFunction);
assertNotNull(result);
Optional<HandlerFunction<Void>> resultHandlerFunction = result.route(request);
assertTrue(resultHandlerFunction.isPresent());
assertEquals(handlerFunction, resultHandlerFunction.get());
Mono<HandlerFunction<ServerResponse>> resultHandlerFunction = result.route(request);
StepVerifier.create(resultHandlerFunction)
.expectNext(handlerFunction)
.expectComplete()
.verify();
}
@Test
public void subrouteNoMatch() throws Exception {
HandlerFunction<Void> handlerFunction = request -> ServerResponse.ok().build();
RouterFunction<Void> routerFunction = request -> Optional.of(handlerFunction);
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
RouterFunction<ServerResponse> routerFunction = request -> Mono.just(handlerFunction);
MockServerRequest request = MockServerRequest.builder().build();
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(false);
RouterFunction<Void> result = RouterFunctions.subroute(requestPredicate, routerFunction);
RouterFunction<ServerResponse> result = RouterFunctions.subroute(requestPredicate, routerFunction);
assertNotNull(result);
Optional<HandlerFunction<Void>> resultHandlerFunction = result.route(request);
assertFalse(resultHandlerFunction.isPresent());
Mono<HandlerFunction<ServerResponse>> resultHandlerFunction = result.route(request);
StepVerifier.create(resultHandlerFunction)
.expectComplete()
.verify();
}
@Test
public void toHttpHandler() throws Exception {
HandlerStrategies strategies = mock(HandlerStrategies.class);
when(strategies.messageReaders()).thenReturn(
() -> Collections.<HttpMessageReader<?>>emptyList().stream());
Stream::<HttpMessageReader<?>>empty);
when(strategies.messageWriters()).thenReturn(
() -> Collections.<HttpMessageWriter<?>>emptyList().stream());
Stream::<HttpMessageWriter<?>>empty);
when(strategies.viewResolvers()).thenReturn(
() -> Collections.<ViewResolver>emptyList().stream());
Stream::<ViewResolver>empty);
ServerRequest request = mock(ServerRequest.class);
ServerResponse response = mock(ServerResponse.class);
when(response.writeTo(any(ServerWebExchange.class), eq(strategies))).thenReturn(Mono.empty());
HandlerFunction handlerFunction = mock(HandlerFunction.class);
when(handlerFunction.handle(any(ServerRequest.class))).thenReturn(response);
HandlerFunction<ServerResponse> handlerFunction = mock(HandlerFunction.class);
when(handlerFunction.handle(any(ServerRequest.class))).thenReturn(Mono.just(response));
RouterFunction routerFunction = mock(RouterFunction.class);
when(routerFunction.route(any(ServerRequest.class))).thenReturn(Optional.of(handlerFunction));
RouterFunction<ServerResponse> routerFunction = mock(RouterFunction.class);
when(routerFunction.route(any(ServerRequest.class))).thenReturn(Mono.just(handlerFunction));
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(false);

View File

@@ -20,7 +20,6 @@ import java.time.Duration;
import org.junit.Before;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -127,18 +126,18 @@ public class SseHandlerFunctionIntegrationTests
private static class SseHandler {
public ServerResponse<Publisher<String>> string(ServerRequest request) {
public Mono<ServerResponse> string(ServerRequest request) {
Flux<String> flux = Flux.interval(Duration.ofMillis(100)).map(l -> "foo " + l).take(2);
return ServerResponse.ok().body(fromServerSentEvents(flux, String.class));
}
public ServerResponse<Publisher<Person>> person(ServerRequest request) {
public Mono<ServerResponse> person(ServerRequest request) {
Flux<Person> flux = Flux.interval(Duration.ofMillis(100))
.map(l -> new Person("foo " + l)).take(2);
return ServerResponse.ok().body(fromServerSentEvents(flux, Person.class));
}
public ServerResponse<Publisher<ServerSentEvent<String>>> sse(ServerRequest request) {
public Mono<ServerResponse> sse(ServerRequest request) {
Flux<ServerSentEvent<String>> flux = Flux.interval(Duration.ofMillis(100))
.map(l -> ServerSentEvent.<String>builder().data("foo")
.id(Long.toString(l))