Support RouterFunction in @EnableWebFlux
This commit removes the RouterFunctions.toHandlerMapping method, in favor of native support for RouterFunctions in @EnableWebFlux configuration classes. In order to accomplish this, the HandlerStrategies components has been repurposed to only be used for the "bare-bones" HttpHandler, while the (newly introduced) RouterFunctionMapping uses the strategies as exposed through WebFluxConfigurationSupport. Furthermore, this commit also introduces support for testing RouterFunctions without resorting to an application context. Issue: SPR-15536
This commit is contained in:
@@ -23,6 +23,8 @@ import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
@@ -40,10 +42,12 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
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.MockServerWebExchange;
|
||||
import org.springframework.web.reactive.function.BodyInserter;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.Assert.*;
|
||||
@@ -190,6 +194,17 @@ public class DefaultEntityResponseBuilderTests {
|
||||
|
||||
MockServerWebExchange exchange = MockServerHttpRequest.get("http://localhost").toExchange();
|
||||
|
||||
ServerResponse.Context context = new ServerResponse.Context() {
|
||||
@Override
|
||||
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
|
||||
return Collections.<HttpMessageWriter<?>>singletonList(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()))::stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<Stream<ViewResolver>> viewResolvers() {
|
||||
return Collections.<ViewResolver>emptyList()::stream;
|
||||
}
|
||||
};
|
||||
HandlerStrategies strategies = HandlerStrategies.empty()
|
||||
.customCodecs(configurer -> configurer.writer(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes())))
|
||||
.build();
|
||||
@@ -200,7 +215,7 @@ public class DefaultEntityResponseBuilderTests {
|
||||
.expectNext(body)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
response.writeTo(exchange, strategies);
|
||||
response.writeTo(exchange, context);
|
||||
})
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
||||
@@ -270,9 +270,9 @@ public class DefaultServerResponseBuilderTests {
|
||||
ServerWebExchange exchange = mock(ServerWebExchange.class);
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
when(exchange.getResponse()).thenReturn(response);
|
||||
HandlerStrategies strategies = mock(HandlerStrategies.class);
|
||||
ServerResponse.Context context = mock(ServerResponse.Context.class);
|
||||
|
||||
result.flatMap(res -> res.writeTo(exchange, strategies)).block();
|
||||
result.flatMap(res -> res.writeTo(exchange, context)).block();
|
||||
|
||||
assertEquals(HttpStatus.CREATED, response.getStatusCode());
|
||||
assertEquals("MyValue", response.getHeaders().getFirst("MyKey"));
|
||||
@@ -287,9 +287,9 @@ public class DefaultServerResponseBuilderTests {
|
||||
ServerWebExchange exchange = mock(ServerWebExchange.class);
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
when(exchange.getResponse()).thenReturn(response);
|
||||
HandlerStrategies strategies = mock(HandlerStrategies.class);
|
||||
ServerResponse.Context context = mock(ServerResponse.Context.class);
|
||||
|
||||
result.flatMap(res -> res.writeTo(exchange, strategies)).block();
|
||||
result.flatMap(res -> res.writeTo(exchange, context)).block();
|
||||
|
||||
StepVerifier.create(response.getBody()).expectComplete().verify();
|
||||
}
|
||||
|
||||
@@ -36,16 +36,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.reactive.DispatcherHandler;
|
||||
import org.springframework.web.reactive.HandlerAdapter;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
import org.springframework.web.reactive.config.WebFluxConfigurationSupport;
|
||||
import org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter;
|
||||
import org.springframework.web.reactive.function.server.support.ServerResponseResultHandler;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromPublisher;
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
|
||||
@@ -108,8 +102,9 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr
|
||||
}
|
||||
|
||||
|
||||
@EnableWebFlux
|
||||
@Configuration
|
||||
static class TestConfiguration extends WebFluxConfigurationSupport {
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
public PersonHandler personHandler() {
|
||||
@@ -122,31 +117,23 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HandlerAdapter handlerAdapter() {
|
||||
return new HandlerFunctionAdapter();
|
||||
public RouterFunction<EntityResponse<Person>> monoRouterFunction(PersonHandler personHandler) {
|
||||
return route(RequestPredicates.GET("/mono"), personHandler::mono);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HandlerMapping handlerMapping() {
|
||||
|
||||
PersonHandler personHandler = personHandler();
|
||||
return RouterFunctions.toHandlerMapping(
|
||||
route(RequestPredicates.GET("/mono"), personHandler::mono)
|
||||
.and(route(RequestPredicates.GET("/flux"), personHandler::flux)));
|
||||
public RouterFunction<ServerResponse> fluxRouterFunction(PersonHandler personHandler) {
|
||||
return route(RequestPredicates.GET("/flux"), personHandler::flux);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServerResponseResultHandler responseResultHandler() {
|
||||
return new ServerResponseResultHandler();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class PersonHandler {
|
||||
|
||||
public Mono<ServerResponse> mono(ServerRequest request) {
|
||||
public Mono<EntityResponse<Person>> mono(ServerRequest request) {
|
||||
Person person = new Person("John");
|
||||
return ServerResponse.ok().body(fromObject(person));
|
||||
return EntityResponse.fromObject(person).build();
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> flux(ServerRequest request) {
|
||||
|
||||
@@ -33,7 +33,6 @@ public class HandlerStrategiesTests {
|
||||
assertEquals(Optional.empty(), strategies.messageReaders().get().findFirst());
|
||||
assertEquals(Optional.empty(), strategies.messageWriters().get().findFirst());
|
||||
assertEquals(Optional.empty(), strategies.viewResolvers().get().findFirst());
|
||||
assertNull(strategies.localeResolver().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -42,7 +41,6 @@ public class HandlerStrategiesTests {
|
||||
assertNotEquals(Optional.empty(), strategies.messageReaders().get().findFirst());
|
||||
assertNotEquals(Optional.empty(), strategies.messageWriters().get().findFirst());
|
||||
assertEquals(Optional.empty(), strategies.viewResolvers().get().findFirst());
|
||||
assertNotNull(strategies.localeResolver().get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ package org.springframework.web.reactive.function.server;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.EnumSet;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
@@ -29,9 +32,11 @@ import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -44,6 +49,25 @@ public class ResourceHandlerFunctionTests {
|
||||
|
||||
private final ResourceHandlerFunction handlerFunction = new ResourceHandlerFunction(this.resource);
|
||||
|
||||
private ServerResponse.Context context;
|
||||
|
||||
@Before
|
||||
public void createContext() {
|
||||
HandlerStrategies strategies = HandlerStrategies.withDefaults();
|
||||
context = new ServerResponse.Context() {
|
||||
@Override
|
||||
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
|
||||
return strategies.messageWriters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<Stream<ViewResolver>> viewResolvers() {
|
||||
return strategies.viewResolvers();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void get() throws IOException {
|
||||
@@ -59,7 +83,7 @@ public class ResourceHandlerFunctionTests {
|
||||
assertTrue(response instanceof EntityResponse);
|
||||
EntityResponse<Resource> entityResponse = (EntityResponse<Resource>) response;
|
||||
assertEquals(this.resource, entityResponse.entity());
|
||||
return response.writeTo(exchange, HandlerStrategies.withDefaults());
|
||||
return response.writeTo(exchange, context);
|
||||
});
|
||||
|
||||
StepVerifier.create(result)
|
||||
@@ -94,7 +118,7 @@ public class ResourceHandlerFunctionTests {
|
||||
assertTrue(response instanceof EntityResponse);
|
||||
EntityResponse<Resource> entityResponse = (EntityResponse<Resource>) response;
|
||||
assertEquals(this.resource.getFilename(), entityResponse.entity().getFilename());
|
||||
return response.writeTo(exchange, HandlerStrategies.withDefaults());
|
||||
return response.writeTo(exchange, context);
|
||||
});
|
||||
|
||||
StepVerifier.create(result).expectComplete().verify();
|
||||
@@ -116,7 +140,7 @@ public class ResourceHandlerFunctionTests {
|
||||
assertEquals(HttpStatus.OK, response.statusCode());
|
||||
assertEquals(EnumSet.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS),
|
||||
response.headers().getAllow());
|
||||
return response.writeTo(exchange, HandlerStrategies.withDefaults());
|
||||
return response.writeTo(exchange, context);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ public class RouterFunctionsTests {
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeTo(ServerWebExchange exchange,
|
||||
HandlerStrategies strategies) {
|
||||
Context context) {
|
||||
return Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, "Not found"));
|
||||
}
|
||||
});
|
||||
@@ -232,7 +232,7 @@ public class RouterFunctionsTests {
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeTo(ServerWebExchange exchange,
|
||||
HandlerStrategies strategies) {
|
||||
Context context) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Not found");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function.server.support;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.codec.ByteBufferDecoder;
|
||||
import org.springframework.http.codec.DecoderHttpMessageReader;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.server.RequestPredicates;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class RouterFunctionMappingTests {
|
||||
|
||||
|
||||
private List<HttpMessageReader<?>> messageReaders;
|
||||
|
||||
private ServerWebExchange exchange;
|
||||
|
||||
private ServerCodecConfigurer codecConfigurer;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.messageReaders =
|
||||
Collections.singletonList(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
|
||||
this.exchange = new MockServerWebExchange(MockServerHttpRequest.get("http://example.com/match").build());
|
||||
codecConfigurer = ServerCodecConfigurer.create();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normal() {
|
||||
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
|
||||
RouterFunction<ServerResponse> routerFunction = request -> Mono.just(handlerFunction);
|
||||
|
||||
RouterFunctionMapping mapping = new RouterFunctionMapping(routerFunction);
|
||||
mapping.setMessageCodecConfigurer(this.codecConfigurer);
|
||||
|
||||
Mono<Object> result = mapping.getHandler(this.exchange);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectNext(handlerFunction)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noMatch() {
|
||||
RouterFunction<ServerResponse> routerFunction = request -> Mono.empty();
|
||||
RouterFunctionMapping mapping = new RouterFunctionMapping(routerFunction);
|
||||
mapping.setMessageCodecConfigurer(this.codecConfigurer);
|
||||
|
||||
Mono<Object> result = mapping.getHandler(this.exchange);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
private static class TestConfig {
|
||||
|
||||
public RouterFunction<ServerResponse> match() {
|
||||
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
|
||||
return RouterFunctions.route(RequestPredicates.GET("/match"), handlerFunction);
|
||||
}
|
||||
|
||||
public RouterFunction<ServerResponse> noMatch() {
|
||||
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
|
||||
RouterFunction<ServerResponse> routerFunction = request -> Mono.empty();
|
||||
return RouterFunctions.route(RequestPredicates.GET("/no-match"), handlerFunction);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user