Browse Source
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-15536pull/469/merge
21 changed files with 647 additions and 247 deletions
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
/* |
||||
* 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.test.web.reactive.server; |
||||
|
||||
import org.springframework.web.reactive.function.server.HandlerStrategies; |
||||
import org.springframework.web.reactive.function.server.RouterFunction; |
||||
import org.springframework.web.reactive.function.server.RouterFunctions; |
||||
import org.springframework.web.server.WebHandler; |
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder; |
||||
|
||||
/** |
||||
* Spec for setting up server-less testing against a RouterFunction. |
||||
* |
||||
* @author Arjen Poutsma |
||||
* @since 5.0 |
||||
*/ |
||||
class DefaultRouterFunctionSpec extends AbstractMockServerSpec<WebTestClient.RouterFunctionSpec> |
||||
implements WebTestClient.RouterFunctionSpec { |
||||
|
||||
private final RouterFunction<?> routerFunction; |
||||
|
||||
private HandlerStrategies handlerStrategies = HandlerStrategies.withDefaults(); |
||||
|
||||
|
||||
DefaultRouterFunctionSpec(RouterFunction<?> routerFunction) { |
||||
this.routerFunction = routerFunction; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public WebTestClient.RouterFunctionSpec handlerStrategies(HandlerStrategies handlerStrategies) { |
||||
if (handlerStrategies != null) { |
||||
this.handlerStrategies = handlerStrategies; |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
protected WebHttpHandlerBuilder initHttpHandlerBuilder() { |
||||
WebHandler webHandler = RouterFunctions.toWebHandler(this.routerFunction, this.handlerStrategies); |
||||
return WebHttpHandlerBuilder.webHandler(webHandler); |
||||
} |
||||
|
||||
} |
||||
@ -1,62 +0,0 @@
@@ -1,62 +0,0 @@
|
||||
/* |
||||
* 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.test.web.reactive.server; |
||||
|
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext; |
||||
import org.springframework.web.reactive.DispatcherHandler; |
||||
import org.springframework.web.reactive.HandlerAdapter; |
||||
import org.springframework.web.reactive.HandlerMapping; |
||||
import org.springframework.web.reactive.HandlerResultHandler; |
||||
import org.springframework.web.reactive.function.server.RouterFunction; |
||||
import org.springframework.web.reactive.function.server.RouterFunctions; |
||||
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; |
||||
|
||||
/** |
||||
* Spec for setting up server-less testing against a RouterFunction. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.0 |
||||
*/ |
||||
public class RouterFunctionSpec extends AbstractMockServerSpec<RouterFunctionSpec> { |
||||
|
||||
private final RouterFunction<?> routerFunction; |
||||
|
||||
|
||||
RouterFunctionSpec(RouterFunction<?> routerFunction) { |
||||
this.routerFunction = routerFunction; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
protected WebHttpHandlerBuilder initHttpHandlerBuilder() { |
||||
return WebHttpHandlerBuilder.applicationContext(initApplicationContext()); |
||||
} |
||||
|
||||
private ApplicationContext initApplicationContext() { |
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); |
||||
context.registerBean("webHandler", DispatcherHandler.class, () -> new DispatcherHandler()); |
||||
context.registerBean(HandlerMapping.class, () -> RouterFunctions.toHandlerMapping(this.routerFunction)); |
||||
context.registerBean(HandlerAdapter.class, () -> new HandlerFunctionAdapter()); |
||||
context.registerBean(HandlerResultHandler.class, () -> new ServerResponseResultHandler()); |
||||
context.refresh(); |
||||
return context; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,136 @@
@@ -0,0 +1,136 @@
|
||||
/* |
||||
* 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 reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.beans.factory.InitializingBean; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.http.codec.ServerCodecConfigurer; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.web.reactive.function.server.RouterFunction; |
||||
import org.springframework.web.reactive.function.server.RouterFunctions; |
||||
import org.springframework.web.reactive.function.server.ServerRequest; |
||||
import org.springframework.web.reactive.handler.AbstractHandlerMapping; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
|
||||
/** |
||||
* {@code HandlerMapping} implementation that supports {@link RouterFunction}s. |
||||
* <p>If no {@link RouterFunction} is provided at |
||||
* {@linkplain #RouterFunctionMapping(RouterFunction) construction time}, this mapping will detect |
||||
* all router functions in the application context, and consult them in |
||||
* {@linkplain org.springframework.core.annotation.Order order}. |
||||
* |
||||
* @author Arjen Poutsma |
||||
* @since 5.0 |
||||
*/ |
||||
public class RouterFunctionMapping extends AbstractHandlerMapping implements InitializingBean { |
||||
|
||||
private RouterFunction<?> routerFunction; |
||||
|
||||
private ServerCodecConfigurer messageCodecConfigurer; |
||||
|
||||
/** |
||||
* Create an empty {@code RouterFunctionMapping}. |
||||
* <p>If this constructor is used, this mapping will detect all {@link RouterFunction} instances |
||||
* available in the application context. |
||||
*/ |
||||
public RouterFunctionMapping() { |
||||
} |
||||
|
||||
/** |
||||
* Create a {@code RouterFunctionMapping} with the given {@link RouterFunction}. |
||||
* <p>If this constructor is used, no application context detection will occur. |
||||
* @param routerFunction the router function to use for mapping |
||||
*/ |
||||
public RouterFunctionMapping(RouterFunction<?> routerFunction) { |
||||
this.routerFunction = routerFunction; |
||||
} |
||||
|
||||
/** |
||||
* Configure HTTP message readers to de-serialize the request body with. |
||||
* <p>By default this is set to {@link ServerCodecConfigurer} with defaults. |
||||
*/ |
||||
public void setMessageCodecConfigurer(ServerCodecConfigurer configurer) { |
||||
this.messageCodecConfigurer = configurer; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void afterPropertiesSet() throws Exception { |
||||
if (this.messageCodecConfigurer == null) { |
||||
this.messageCodecConfigurer = ServerCodecConfigurer.create(); |
||||
} |
||||
if (this.routerFunction == null) { |
||||
initRouterFunctions(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Initialized the router functions by detecting them in the application context. |
||||
*/ |
||||
protected void initRouterFunctions() { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("Looking for router functions in application context: " + |
||||
getApplicationContext()); |
||||
} |
||||
|
||||
List<RouterFunction<?>> routerFunctions = routerFunctions(); |
||||
if (!CollectionUtils.isEmpty(routerFunctions) && logger.isInfoEnabled()) { |
||||
routerFunctions.forEach(routerFunction1 -> { |
||||
logger.info("Mapped " + routerFunction1); |
||||
}); |
||||
} |
||||
this.routerFunction = routerFunctions.stream() |
||||
.reduce(RouterFunction::andOther) |
||||
.orElse(null); |
||||
} |
||||
|
||||
private List<RouterFunction<?>> routerFunctions() { |
||||
SortedRouterFunctionsContainer container = new SortedRouterFunctionsContainer(); |
||||
getApplicationContext().getAutowireCapableBeanFactory().autowireBean(container); |
||||
|
||||
return CollectionUtils.isEmpty(container.routerFunctions) ? Collections.emptyList() : |
||||
container.routerFunctions; |
||||
} |
||||
|
||||
@Override |
||||
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) { |
||||
if (this.routerFunction != null) { |
||||
ServerRequest request = ServerRequest.create(exchange, this.messageCodecConfigurer.getReaders()); |
||||
exchange.getAttributes().put(RouterFunctions.REQUEST_ATTRIBUTE, request); |
||||
return this.routerFunction.route(request); |
||||
} |
||||
else { |
||||
return Mono.empty(); |
||||
} |
||||
} |
||||
|
||||
private static class SortedRouterFunctionsContainer { |
||||
|
||||
private List<RouterFunction<?>> routerFunctions; |
||||
|
||||
@Autowired(required = false) |
||||
public void setRouterFunctions(List<RouterFunction<?>> routerFunctions) { |
||||
this.routerFunctions = routerFunctions; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,108 @@
@@ -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); |
||||
} |
||||
|
||||
} |
||||
} |
||||
Loading…
Reference in new issue