Browse Source

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
pull/469/merge
Arjen Poutsma 9 years ago
parent
commit
01e3561db9
  1. 58
      spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultRouterFunctionSpec.java
  2. 62
      spring-test/src/main/java/org/springframework/test/web/reactive/server/RouterFunctionSpec.java
  3. 17
      spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java
  4. 54
      spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java
  5. 4
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilder.java
  6. 32
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java
  7. 19
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseBuilder.java
  8. 14
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java
  9. 17
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/HandlerStrategies.java
  10. 168
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java
  11. 15
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java
  12. 31
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java
  13. 136
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java
  14. 69
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/ServerResponseResultHandler.java
  15. 17
      spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilderTests.java
  16. 8
      spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilderTests.java
  17. 29
      spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java
  18. 2
      spring-webflux/src/test/java/org/springframework/web/reactive/function/server/HandlerStrategiesTests.java
  19. 30
      spring-webflux/src/test/java/org/springframework/web/reactive/function/server/ResourceHandlerFunctionTests.java
  20. 4
      spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionsTests.java
  21. 108
      spring-webflux/src/test/java/org/springframework/web/reactive/function/server/support/RouterFunctionMappingTests.java

58
spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultRouterFunctionSpec.java

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

62
spring-test/src/main/java/org/springframework/test/web/reactive/server/RouterFunctionSpec.java

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

17
spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java

@ -48,6 +48,7 @@ import org.springframework.web.reactive.function.client.ExchangeFilterFunction; @@ -48,6 +48,7 @@ import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
import org.springframework.web.server.ServerWebExchange;
@ -169,8 +170,8 @@ public interface WebTestClient { @@ -169,8 +170,8 @@ public interface WebTestClient {
* @param routerFunction the RouterFunction to test
* @return the {@link WebTestClient} builder
*/
static MockServerSpec<?> bindToRouterFunction(RouterFunction<?> routerFunction) {
return new RouterFunctionSpec(routerFunction);
static RouterFunctionSpec bindToRouterFunction(RouterFunction<?> routerFunction) {
return new DefaultRouterFunctionSpec(routerFunction);
}
/**
@ -285,6 +286,18 @@ public interface WebTestClient { @@ -285,6 +286,18 @@ public interface WebTestClient {
}
/**
* Specification for customizing router function configuration.
*/
interface RouterFunctionSpec extends MockServerSpec<RouterFunctionSpec> {
/**
* Configure handler strategies.
*/
RouterFunctionSpec handlerStrategies(HandlerStrategies handlerStrategies);
}
/**
* Steps for customizing the {@link WebClient} used to test with
* internally delegating to a {@link WebClient.Builder}.

54
spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java

@ -47,6 +47,9 @@ import org.springframework.web.reactive.DispatcherHandler; @@ -47,6 +47,9 @@ import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter;
import org.springframework.web.reactive.function.server.support.RouterFunctionMapping;
import org.springframework.web.reactive.function.server.support.ServerResponseResultHandler;
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
import org.springframework.web.reactive.result.SimpleHandlerAdapter;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
@ -81,6 +84,8 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { @@ -81,6 +84,8 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
private PathMatchConfigurer pathMatchConfigurer;
private ViewResolverRegistry viewResolverRegistry;
private ApplicationContext applicationContext;
@ -203,6 +208,23 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { @@ -203,6 +208,23 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
public void configurePathMatching(PathMatchConfigurer configurer) {
}
@Bean
public RouterFunctionMapping routerFunctionMapping() {
RouterFunctionMapping mapping = createRouterFunctionMapping();
mapping.setOrder(-1); // go before RequestMappingHandlerMapping
mapping.setMessageCodecConfigurer(serverCodecConfigurer());
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
/**
* Override to plug a sub-class of {@link RouterFunctionMapping}.
*/
protected RouterFunctionMapping createRouterFunctionMapping() {
return new RouterFunctionMapping();
}
/**
* Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
* resource handlers. To configure resource handling, override
@ -362,6 +384,11 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { @@ -362,6 +384,11 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
return null;
}
@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {
return new HandlerFunctionAdapter();
}
@Bean
public SimpleHandlerAdapter simpleHandlerAdapter() {
return new SimpleHandlerAdapter();
@ -381,15 +408,38 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { @@ -381,15 +408,38 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
@Bean
public ViewResolutionResultHandler viewResolutionResultHandler() {
ViewResolverRegistry registry = new ViewResolverRegistry(getApplicationContext());
configureViewResolvers(registry);
ViewResolverRegistry registry = getViewResolverRegistry();
List<ViewResolver> resolvers = registry.getViewResolvers();
ViewResolutionResultHandler handler = new ViewResolutionResultHandler(
resolvers, webFluxContentTypeResolver(), webFluxAdapterRegistry());
handler.setDefaultViews(registry.getDefaultViews());
handler.setOrder(registry.getOrder());
return handler;
}
@Bean
public ServerResponseResultHandler serverResponseResultHandler() {
ViewResolverRegistry registry = getViewResolverRegistry();
List<ViewResolver> resolvers = registry.getViewResolvers();
ServerResponseResultHandler handler = new ServerResponseResultHandler();
handler.setMessageCodecConfigurer(serverCodecConfigurer());
handler.setViewResolvers(resolvers);
handler.setOrder(registry.getOrder() + 1);
return handler;
}
/**
* Callback for building the {@link ViewResolverRegistry}. This method is final,
* use {@link #configureViewResolvers} to customize view resolvers.
*/
protected final ViewResolverRegistry getViewResolverRegistry() {
if (this.viewResolverRegistry == null) {
this.viewResolverRegistry = new ViewResolverRegistry(getApplicationContext());
configureViewResolvers(this.viewResolverRegistry);
}
return this.viewResolverRegistry;
}
/**

4
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilder.java

@ -203,13 +203,13 @@ class DefaultEntityResponseBuilder<T> implements EntityResponse.Builder<T> { @@ -203,13 +203,13 @@ class DefaultEntityResponseBuilder<T> implements EntityResponse.Builder<T> {
}
@Override
public Mono<Void> writeTo(ServerWebExchange exchange, HandlerStrategies strategies) {
public Mono<Void> writeTo(ServerWebExchange exchange, Context context) {
ServerHttpResponse response = exchange.getResponse();
writeStatusAndHeaders(response);
return inserter().insert(response, new BodyInserter.Context() {
@Override
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
return strategies.messageWriters();
return context.messageWriters();
}
@Override

32
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java

@ -19,10 +19,7 @@ package org.springframework.web.reactive.function.server; @@ -19,10 +19,7 @@ package org.springframework.web.reactive.function.server;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
@ -44,18 +41,10 @@ import org.springframework.web.server.handler.ResponseStatusExceptionHandler; @@ -44,18 +41,10 @@ import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
*/
class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
static final Function<ServerRequest, Optional<Locale>> DEFAULT_LOCALE_RESOLVER =
request -> request.headers().acceptLanguage().stream()
.map(Locale.LanguageRange::getRange)
.map(Locale::forLanguageTag).findFirst();
private final ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create();
private final List<ViewResolver> viewResolvers = new ArrayList<>();
private Function<ServerRequest, Optional<Locale>> localeResolver;
private final List<WebFilter> webFilters = new ArrayList<>();
private final List<WebExceptionHandler> exceptionHandlers = new ArrayList<>();
@ -67,7 +56,6 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder { @@ -67,7 +56,6 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
public void defaultConfiguration() {
this.codecConfigurer.registerDefaults(true);
localeResolver(DEFAULT_LOCALE_RESOLVER);
exceptionHandler(new ResponseStatusExceptionHandler());
}
@ -94,13 +82,6 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder { @@ -94,13 +82,6 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
return this;
}
@Override
public HandlerStrategies.Builder localeResolver(Function<ServerRequest, Optional<Locale>> localeResolver) {
Assert.notNull(localeResolver, "'localeResolver' must not be null");
this.localeResolver = localeResolver;
return this;
}
@Override
public HandlerStrategies.Builder webFilter(WebFilter filter) {
Assert.notNull(filter, "'filter' must not be null");
@ -118,8 +99,8 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder { @@ -118,8 +99,8 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
@Override
public HandlerStrategies build() {
return new DefaultHandlerStrategies(this.codecConfigurer.getReaders(),
this.codecConfigurer.getWriters(), this.viewResolvers, this.localeResolver,
this.webFilters, this.exceptionHandlers);
this.codecConfigurer.getWriters(), this.viewResolvers, this.webFilters,
this.exceptionHandlers);
}
@ -131,8 +112,6 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder { @@ -131,8 +112,6 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
private final List<ViewResolver> viewResolvers;
private final Function<ServerRequest, Optional<Locale>> localeResolver;
private final List<WebFilter> webFilters;
private final List<WebExceptionHandler> exceptionHandlers;
@ -141,14 +120,12 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder { @@ -141,14 +120,12 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
List<HttpMessageReader<?>> messageReaders,
List<HttpMessageWriter<?>> messageWriters,
List<ViewResolver> viewResolvers,
Function<ServerRequest, Optional<Locale>> localeResolver,
List<WebFilter> webFilters,
List<WebExceptionHandler> exceptionHandlers) {
this.messageReaders = unmodifiableCopy(messageReaders);
this.messageWriters = unmodifiableCopy(messageWriters);
this.viewResolvers = unmodifiableCopy(viewResolvers);
this.localeResolver = localeResolver;
this.webFilters = unmodifiableCopy(webFilters);
this.exceptionHandlers = unmodifiableCopy(exceptionHandlers);
}
@ -172,11 +149,6 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder { @@ -172,11 +149,6 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
return this.viewResolvers::stream;
}
@Override
public Supplier<Function<ServerRequest, Optional<Locale>>> localeResolver() {
return () -> this.localeResolver;
}
@Override
public Supplier<Stream<WebFilter>> webFilters() {
return this.webFilters::stream;

19
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseBuilder.java

@ -20,6 +20,7 @@ import java.util.Arrays; @@ -20,6 +20,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Stream;
@ -158,12 +159,12 @@ class DefaultRenderingResponseBuilder implements RenderingResponse.Builder { @@ -158,12 +159,12 @@ class DefaultRenderingResponseBuilder implements RenderingResponse.Builder {
}
@Override
public Mono<Void> writeTo(ServerWebExchange exchange, HandlerStrategies strategies) {
public Mono<Void> writeTo(ServerWebExchange exchange, Context context) {
ServerHttpResponse response = exchange.getResponse();
writeStatusAndHeaders(response);
MediaType contentType = exchange.getResponse().getHeaders().getContentType();
Locale locale = resolveLocale(exchange, strategies);
Stream<ViewResolver> viewResolverStream = strategies.viewResolvers().get();
Locale locale = resolveLocale(exchange);
Stream<ViewResolver> viewResolverStream = context.viewResolvers().get();
return Flux.fromStream(viewResolverStream)
.concatMap(viewResolver -> viewResolver.resolveViewName(name(), locale))
@ -173,15 +174,9 @@ class DefaultRenderingResponseBuilder implements RenderingResponse.Builder { @@ -173,15 +174,9 @@ class DefaultRenderingResponseBuilder implements RenderingResponse.Builder {
.flatMap(view -> view.render(model(), contentType, exchange));
}
private Locale resolveLocale(ServerWebExchange exchange, HandlerStrategies strategies) {
ServerRequest request =
exchange.<ServerRequest>getAttribute(RouterFunctions.REQUEST_ATTRIBUTE)
.orElseThrow(() -> new IllegalStateException(
"Could not find ServerRequest in exchange attributes"));
return strategies.localeResolver().get()
.apply(request)
.orElse(Locale.getDefault());
private Locale resolveLocale(ServerWebExchange exchange) {
List<Locale> locales = exchange.getRequest().getHeaders().getAcceptLanguageAsLocales();
return locales.isEmpty() ? Locale.getDefault() : locales.get(0);
}
}

14
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java

@ -169,7 +169,7 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { @@ -169,7 +169,7 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
@Override
public Mono<ServerResponse> build(
BiFunction<ServerWebExchange, HandlerStrategies, Mono<Void>> writeFunction) {
BiFunction<ServerWebExchange, ServerResponse.Context, Mono<Void>> writeFunction) {
Assert.notNull(writeFunction, "'writeFunction' must not be null");
return Mono.just(new WriterFunctionServerResponse(this.statusCode, this.headers, writeFunction));
@ -277,19 +277,19 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { @@ -277,19 +277,19 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
private static final class WriterFunctionServerResponse extends AbstractServerResponse {
private final BiFunction<ServerWebExchange, HandlerStrategies, Mono<Void>> writeFunction;
private final BiFunction<ServerWebExchange, Context, Mono<Void>> writeFunction;
public WriterFunctionServerResponse(HttpStatus statusCode, HttpHeaders headers,
BiFunction<ServerWebExchange, HandlerStrategies, Mono<Void>> writeFunction) {
BiFunction<ServerWebExchange, Context, Mono<Void>> writeFunction) {
super(statusCode, headers);
this.writeFunction = writeFunction;
}
@Override
public Mono<Void> writeTo(ServerWebExchange exchange, HandlerStrategies strategies) {
public Mono<Void> writeTo(ServerWebExchange exchange, Context context) {
writeStatusAndHeaders(exchange.getResponse());
return this.writeFunction.apply(exchange, strategies);
return this.writeFunction.apply(exchange, context);
}
}
@ -309,13 +309,13 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { @@ -309,13 +309,13 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
}
@Override
public Mono<Void> writeTo(ServerWebExchange exchange, HandlerStrategies strategies) {
public Mono<Void> writeTo(ServerWebExchange exchange, Context context) {
ServerHttpResponse response = exchange.getResponse();
writeStatusAndHeaders(response);
return this.inserter.insert(response, new BodyInserter.Context() {
@Override
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
return strategies.messageWriters();
return context.messageWriters();
}
@Override

17
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/HandlerStrategies.java

@ -16,10 +16,7 @@ @@ -16,10 +16,7 @@
package org.springframework.web.reactive.function.server;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
@ -40,7 +37,6 @@ import org.springframework.web.server.WebFilter; @@ -40,7 +37,6 @@ import org.springframework.web.server.WebFilter;
* @author Juergen Hoeller
* @since 5.0
* @see RouterFunctions#toHttpHandler(RouterFunction, HandlerStrategies)
* @see RouterFunctions#toHandlerMapping(RouterFunction, HandlerStrategies)
*/
public interface HandlerStrategies {
@ -67,12 +63,6 @@ public interface HandlerStrategies { @@ -67,12 +63,6 @@ public interface HandlerStrategies {
*/
Supplier<Stream<ViewResolver>> viewResolvers();
/**
* Supply a function that resolves the locale of a given {@link ServerRequest}.
* @return the locale resolver
*/
Supplier<Function<ServerRequest, Optional<Locale>>> localeResolver();
/**
* Supply a {@linkplain Stream stream} of {@link WebFilter}s to be used for filtering the
* request and response.
@ -147,13 +137,6 @@ public interface HandlerStrategies { @@ -147,13 +137,6 @@ public interface HandlerStrategies {
*/
Builder viewResolver(ViewResolver viewResolver);
/**
* Set the given function as {@link Locale} resolver for this builder.
* @param localeResolver the locale resolver to set
* @return this builder
*/
Builder localeResolver(Function<ServerRequest, Optional<Locale>> localeResolver);
/**
* Add the given web filter to this builder.
* @param filter the filter to add

168
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java

@ -19,17 +19,17 @@ package org.springframework.web.reactive.function.server; @@ -19,17 +19,17 @@ package org.springframework.web.reactive.function.server;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.core.io.Resource;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.util.Assert;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter;
import org.springframework.web.reactive.function.server.support.ServerResponseResultHandler;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
@ -92,18 +92,7 @@ public abstract class RouterFunctions { @@ -92,18 +92,7 @@ public abstract class RouterFunctions {
Assert.notNull(predicate, "'predicate' must not be null");
Assert.notNull(handlerFunction, "'handlerFunction' must not be null");
return request -> {
if (predicate.test(request)) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Predicate \"%s\" matches against \"%s\"",
predicate, request));
}
return Mono.just(handlerFunction);
}
else {
return Mono.empty();
}
};
return new DefaultRouterFunction<>(predicate, handlerFunction);
}
/**
@ -135,17 +124,7 @@ public abstract class RouterFunctions { @@ -135,17 +124,7 @@ public abstract class RouterFunctions {
Assert.notNull(predicate, "'predicate' must not be null");
Assert.notNull(routerFunction, "'routerFunction' must not be null");
return request -> predicate.nest(request)
.map(nestedRequest -> {
if (logger.isDebugEnabled()) {
logger.debug(
String.format("Nested predicate \"%s\" matches against \"%s\"",
predicate, request));
}
return routerFunction.route(nestedRequest);
}
)
.orElseGet(Mono::empty);
return new DefaultNestedRouterFunction<>(predicate, routerFunction);
}
/**
@ -219,19 +198,32 @@ public abstract class RouterFunctions { @@ -219,19 +198,32 @@ public abstract class RouterFunctions {
Assert.notNull(routerFunction, "RouterFunction must not be null");
Assert.notNull(strategies, "HandlerStrategies must not be null");
WebHandler webHandler = exchange -> {
WebHandler webHandler = toWebHandler(routerFunction, strategies);
WebHttpHandlerBuilder handlerBuilder = WebHttpHandlerBuilder.webHandler(webHandler);
strategies.webFilters().get().forEach(handlerBuilder::filter);
strategies.exceptionHandlers().get().forEach(handlerBuilder::exceptionHandler);
return handlerBuilder.build();
}
/**
* Convert the given {@linkplain RouterFunction router function} into a {@link WebHandler},
* using the given strategies.
* @param routerFunction the router function to convert
* @param strategies the strategies to use
* @return a web handler that handles web request using the given router function
*/
public static WebHandler toWebHandler(RouterFunction<?> routerFunction, HandlerStrategies strategies) {
Assert.notNull(routerFunction, "RouterFunction must not be null");
Assert.notNull(strategies, "HandlerStrategies must not be null");
return exchange -> {
ServerRequest request = new DefaultServerRequest(exchange, strategies.messageReaders());
addAttributes(exchange, request);
return routerFunction.route(request)
.defaultIfEmpty(notFound())
.flatMap(handlerFunction -> wrapException(() -> handlerFunction.handle(request)))
.flatMap(response -> wrapException(() -> response.writeTo(exchange, strategies)));
.flatMap(response -> wrapException(() -> response.writeTo(exchange, toContext(strategies))));
};
WebHttpHandlerBuilder handlerBuilder = WebHttpHandlerBuilder.webHandler(webHandler);
strategies.webFilters().get().forEach(handlerBuilder::filter);
strategies.exceptionHandlers().get().forEach(handlerBuilder::exceptionHandler);
return handlerBuilder.build();
}
private static <T> Mono<T> wrapException(Supplier<Mono<T>> supplier) {
@ -243,39 +235,17 @@ public abstract class RouterFunctions { @@ -243,39 +235,17 @@ public abstract class RouterFunctions {
}
}
/**
* Convert the given {@code RouterFunction} into a {@code HandlerMapping}.
* This conversion uses {@linkplain HandlerStrategies#builder() default strategies}.
* <p>The returned {@code HandlerMapping} can be run in a
* {@link org.springframework.web.reactive.DispatcherHandler}.
* @param routerFunction the router function to convert
* @return an handler mapping that maps HTTP request to a handler using the given router function
* @see HandlerFunctionAdapter
* @see ServerResponseResultHandler
*/
public static HandlerMapping toHandlerMapping(RouterFunction<?> routerFunction) {
return toHandlerMapping(routerFunction, HandlerStrategies.withDefaults());
}
/**
* Convert the given {@linkplain RouterFunction router function} into a {@link HandlerMapping},
* using the given strategies.
* <p>The returned {@code HandlerMapping} can be run in a
* {@link org.springframework.web.reactive.DispatcherHandler}.
* @param routerFunction the router function to convert
* @param strategies the strategies to use
* @return an handler mapping that maps HTTP request to a handler using the given router function
* @see HandlerFunctionAdapter
* @see ServerResponseResultHandler
*/
public static HandlerMapping toHandlerMapping(RouterFunction<?> routerFunction, HandlerStrategies strategies) {
Assert.notNull(routerFunction, "RouterFunction must not be null");
Assert.notNull(strategies, "HandlerStrategies must not be null");
private static ServerResponse.Context toContext(HandlerStrategies strategies) {
return new ServerResponse.Context() {
@Override
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
return strategies.messageWriters();
}
return exchange -> {
ServerRequest request = new DefaultServerRequest(exchange, strategies.messageReaders());
addAttributes(exchange, request);
return routerFunction.route(request).map(handlerFunction -> (Object)handlerFunction);
@Override
public Supplier<Stream<ViewResolver>> viewResolvers() {
return strategies.viewResolvers();
}
};
}
@ -294,4 +264,72 @@ public abstract class RouterFunctions { @@ -294,4 +264,72 @@ public abstract class RouterFunctions {
return (HandlerFunction<T>) handlerFunction;
}
private static class DefaultRouterFunction<T extends ServerResponse>
implements RouterFunction<T> {
private final RequestPredicate predicate;
private final HandlerFunction<T> handlerFunction;
public DefaultRouterFunction(RequestPredicate predicate,
HandlerFunction<T> handlerFunction) {
this.predicate = predicate;
this.handlerFunction = handlerFunction;
}
@Override
public Mono<HandlerFunction<T>> route(ServerRequest request) {
if (this.predicate.test(request)) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Predicate \"%s\" matches against \"%s\"",
this.predicate, request));
}
return Mono.just(this.handlerFunction);
}
else {
return Mono.empty();
}
}
@Override
public String toString() {
return String.format("%s -> %s", this.predicate, this.handlerFunction);
}
}
private static class DefaultNestedRouterFunction<T extends ServerResponse>
implements RouterFunction<T> {
private final RequestPredicate predicate;
private final RouterFunction<T> routerFunction;
public DefaultNestedRouterFunction(RequestPredicate predicate,
RouterFunction<T> routerFunction) {
this.predicate = predicate;
this.routerFunction = routerFunction;
}
@Override
public Mono<HandlerFunction<T>> route(ServerRequest serverRequest) {
return this.predicate.nest(serverRequest)
.map(nestedRequest -> {
if (logger.isDebugEnabled()) {
logger.debug(
String.format(
"Nested predicate \"%s\" matches against \"%s\"",
this.predicate, serverRequest));
}
return this.routerFunction.route(nestedRequest);
}
)
.orElseGet(Mono::empty);
}
@Override
public String toString() {
return String.format("%s -> %s", this.predicate, this.routerFunction);
}
}
}

15
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java

@ -33,9 +33,11 @@ import org.springframework.http.HttpHeaders; @@ -33,9 +33,11 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRange;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.json.Jackson2CodecSupport;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
/**
@ -175,6 +177,19 @@ public interface ServerRequest { @@ -175,6 +177,19 @@ public interface ServerRequest {
Mono<? extends Principal> principal();
/**
* Create a new {@code ServerRequest} based on the given {@code ServerWebExchange} and
* message readers.
* @param exchange the exchange
* @param messageReaders the message readers
* @return the created {@code ServerRequest}
*/
static ServerRequest create(ServerWebExchange exchange,
List<HttpMessageReader<?>> messageReaders) {
return new DefaultServerRequest(exchange, messageReaders::stream);
}
/**
* Represents the headers of the HTTP request.
* @see ServerRequest#headers()

31
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java

@ -22,6 +22,8 @@ import java.util.Collection; @@ -22,6 +22,8 @@ import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
@ -31,11 +33,13 @@ import org.springframework.http.HttpHeaders; @@ -31,11 +33,13 @@ 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.HttpMessageWriter;
import org.springframework.http.codec.json.Jackson2CodecSupport;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
/**
@ -64,10 +68,10 @@ public interface ServerResponse { @@ -64,10 +68,10 @@ public interface ServerResponse {
/**
* Write this response to the given web exchange.
* @param exchange the web exchange to write to
* @param strategies the strategies to use when writing
* @param context the context to use when writing
* @return {@code Mono<Void>} to indicate when writing is complete
*/
Mono<Void> writeTo(ServerWebExchange exchange, HandlerStrategies strategies);
Mono<Void> writeTo(ServerWebExchange exchange, Context context);
// Static builder methods
@ -298,7 +302,7 @@ public interface ServerResponse { @@ -298,7 +302,7 @@ public interface ServerResponse {
* @param writeFunction the function used to write to the {@link ServerWebExchange}
* @return the built response
*/
Mono<ServerResponse> build(BiFunction<ServerWebExchange, HandlerStrategies, Mono<Void>> writeFunction);
Mono<ServerResponse> build(BiFunction<ServerWebExchange, Context, Mono<Void>> writeFunction);
}
@ -385,4 +389,25 @@ public interface ServerResponse { @@ -385,4 +389,25 @@ public interface ServerResponse {
Mono<ServerResponse> render(String name, Map<String, ?> model);
}
/**
* Defines the context used during the {@link #writeTo(ServerWebExchange, Context)}.
*/
interface Context {
/**
* Supply a {@linkplain Stream stream} of {@link HttpMessageWriter}s
* to be used for response body conversion.
* @return the stream of message writers
*/
Supplier<Stream<HttpMessageWriter<?>>> messageWriters();
/**
* Supply a {@linkplain Stream stream} of {@link ViewResolver}s to be used for view name
* resolution.
* @return the stream of view resolvers
*/
Supplier<Stream<ViewResolver>> viewResolvers();
}
}

136
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java

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

69
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/ServerResponseResultHandler.java

@ -16,13 +16,22 @@ @@ -16,13 +16,22 @@
package org.springframework.web.reactive.function.server.support;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.Ordered;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.util.Assert;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.HandlerResultHandler;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
/**
@ -31,26 +40,53 @@ import org.springframework.web.server.ServerWebExchange; @@ -31,26 +40,53 @@ import org.springframework.web.server.ServerWebExchange;
* @author Arjen Poutsma
* @since 5.0
*/
public class ServerResponseResultHandler implements HandlerResultHandler {
public class ServerResponseResultHandler implements HandlerResultHandler, InitializingBean,
Ordered {
private ServerCodecConfigurer messageCodecConfigurer;
private final HandlerStrategies strategies;
private List<ViewResolver> viewResolvers;
private int order = LOWEST_PRECEDENCE;
/**
* Create a {@code ServerResponseResultHandler} with default strategies.
* Configure HTTP message readers to de-serialize the request body with.
* <p>By default this is set to {@link ServerCodecConfigurer} with defaults.
*/
public ServerResponseResultHandler() {
this(HandlerStrategies.builder().build());
public void setMessageCodecConfigurer(ServerCodecConfigurer configurer) {
this.messageCodecConfigurer = configurer;
}
public void setViewResolvers(List<ViewResolver> viewResolvers) {
this.viewResolvers = viewResolvers;
}
/**
* Create a {@code ServerResponseResultHandler} with the given strategies.
* Set the order for this result handler relative to others.
* <p>By default set to {@link Ordered#LOWEST_PRECEDENCE}, however see
* Javadoc of sub-classes which may change this default.
* @param order the order
*/
public ServerResponseResultHandler(HandlerStrategies strategies) {
Assert.notNull(strategies, "HandlerStrategies must not be null");
this.strategies = strategies;
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.messageCodecConfigurer == null) {
throw new IllegalArgumentException("'messageCodecConfigurer' is required");
}
if (this.viewResolvers == null) {
this.viewResolvers = Collections.emptyList();
}
}
@Override
public boolean supports(HandlerResult result) {
@ -61,7 +97,16 @@ public class ServerResponseResultHandler implements HandlerResultHandler { @@ -61,7 +97,16 @@ public class ServerResponseResultHandler implements HandlerResultHandler {
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
ServerResponse response = (ServerResponse) result.getReturnValue();
Assert.state(response != null, "No ServerResponse");
return response.writeTo(exchange, this.strategies);
}
return response.writeTo(exchange, new ServerResponse.Context() {
@Override
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
return messageCodecConfigurer.getWriters()::stream;
}
@Override
public Supplier<Stream<ViewResolver>> viewResolvers() {
return viewResolvers::stream;
}
});
}
}

17
spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilderTests.java

@ -23,6 +23,8 @@ import java.util.EnumSet; @@ -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; @@ -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 { @@ -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 { @@ -200,7 +215,7 @@ public class DefaultEntityResponseBuilderTests {
.expectNext(body)
.expectComplete()
.verify();
response.writeTo(exchange, strategies);
response.writeTo(exchange, context);
})
.expectComplete()
.verify();

8
spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilderTests.java

@ -270,9 +270,9 @@ public class DefaultServerResponseBuilderTests { @@ -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 { @@ -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();
}

29
spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java

@ -36,16 +36,10 @@ import org.springframework.web.bind.annotation.RequestMapping; @@ -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 @@ -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 @@ -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) {

2
spring-webflux/src/test/java/org/springframework/web/reactive/function/server/HandlerStrategiesTests.java

@ -33,7 +33,6 @@ public class HandlerStrategiesTests { @@ -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 { @@ -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());
}
}

30
spring-webflux/src/test/java/org/springframework/web/reactive/function/server/ResourceHandlerFunctionTests.java

@ -19,7 +19,10 @@ package org.springframework.web.reactive.function.server; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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);
});

4
spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionsTests.java

@ -199,7 +199,7 @@ public class RouterFunctionsTests { @@ -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 { @@ -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");
}
});

108
spring-webflux/src/test/java/org/springframework/web/reactive/function/server/support/RouterFunctionMappingTests.java

@ -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…
Cancel
Save