Add WebFlux auto-configuration

This commit creates auto-configuration classes for both the
annotation and functional variants of the WebFlux framework.

They provide the basic support to get started with those, by
creating the required `HttpHandler` using the provided application
context (for annotation) or `RouterFunction`s (for functional).

They do support `WebFilter` registration and a few advanced
features such as resource handling, `messageReaders|Writers`
and `ViewResolver` auto-registration.

Closes gh-8386
pull/8410/head
Brian Clozel 8 years ago
parent 656b509f03
commit 8317977e1b

@ -354,6 +354,11 @@
<artifactId>spring-websocket</artifactId> <artifactId>spring-websocket</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId> <artifactId>spring-webmvc</artifactId>

@ -0,0 +1,263 @@
/*
* Copyright 2012-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.boot.autoconfigure.webflux;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.CacheControl;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.ResourceChainRegistration;
import org.springframework.web.reactive.config.ResourceHandlerRegistration;
import org.springframework.web.reactive.config.ResourceHandlerRegistry;
import org.springframework.web.reactive.config.ViewResolverRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurationSupport;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.resource.AppCacheManifestTransformer;
import org.springframework.web.reactive.resource.GzipResourceResolver;
import org.springframework.web.reactive.resource.ResourceResolver;
import org.springframework.web.reactive.resource.VersionResourceResolver;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link EnableWebFlux WebFlux}.
*
* @author Brian Clozel
* @author Rob Winch
*/
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass({DispatcherHandler.class, HttpHandler.class})
@ConditionalOnMissingBean({RouterFunction.class, HttpHandler.class})
@AutoConfigureAfter(ReactiveWebServerAutoConfiguration.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebFluxAnnotationAutoConfiguration {
@Configuration
@ConditionalOnMissingBean(WebFluxConfigurationSupport.class)
@EnableConfigurationProperties({ResourceProperties.class, WebFluxProperties.class})
@Import(DelegatingWebFluxConfiguration.class)
public static class WebFluxConfig implements WebFluxConfigurer {
private static final Log logger = LogFactory.getLog(WebFluxConfig.class);
private final ResourceProperties resourceProperties;
private final WebFluxProperties webFluxProperties;
private final ListableBeanFactory beanFactory;
private final List<HandlerMethodArgumentResolver> argumentResolvers;
private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
private final List<ViewResolver> viewResolvers;
public WebFluxConfig(ResourceProperties resourceProperties,
WebFluxProperties webFluxProperties, ListableBeanFactory beanFactory,
ObjectProvider<List<HandlerMethodArgumentResolver>> resolvers,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizer,
ObjectProvider<List<ViewResolver>> viewResolvers) {
this.resourceProperties = resourceProperties;
this.webFluxProperties = webFluxProperties;
this.beanFactory = beanFactory;
this.argumentResolvers = resolvers.getIfAvailable();
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizer.getIfAvailable();
this.viewResolvers = viewResolvers.getIfAvailable();
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
if (this.argumentResolvers != null) {
resolvers.addAll(this.argumentResolvers);
}
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Integer cachePeriod = this.resourceProperties.getCachePeriod();
if (!registry.hasMappingForPattern("/webjars/**")) {
ResourceHandlerRegistration registration = registry
.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
if (cachePeriod != null) {
registration.setCacheControl(CacheControl.maxAge(cachePeriod, TimeUnit.SECONDS));
}
customizeResourceHandlerRegistration(registration);
}
String staticPathPattern = this.webFluxProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
ResourceHandlerRegistration registration = registry.addResourceHandler(staticPathPattern)
.addResourceLocations(this.resourceProperties.getStaticLocations());
if (cachePeriod != null) {
registration.setCacheControl(CacheControl.maxAge(cachePeriod, TimeUnit.SECONDS));
}
customizeResourceHandlerRegistration(registration);
}
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
if (this.viewResolvers != null) {
AnnotationAwareOrderComparator.sort(this.viewResolvers);
this.viewResolvers.forEach(resolver -> registry.viewResolver(resolver));
}
}
@Override
public void addFormatters(final FormatterRegistry registry) {
for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
registry.addConverter(converter);
}
for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
registry.addConverter(converter);
}
for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
registry.addFormatter(formatter);
}
}
private <T> Collection<T> getBeansOfType(Class<T> type) {
return this.beanFactory.getBeansOfType(type).values();
}
private void customizeResourceHandlerRegistration(
ResourceHandlerRegistration registration) {
if (this.resourceHandlerRegistrationCustomizer != null) {
this.resourceHandlerRegistrationCustomizer.customize(registration);
}
}
}
@Configuration
@Import(WebFluxConfig.class)
public static class WebHttpHandlerConfiguration implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Bean
public HttpHandler httpHandler() {
return WebHttpHandlerBuilder.applicationContext(this.applicationContext).build();
}
}
@Configuration
@ConditionalOnEnabledResourceChain
static class ResourceChainCustomizerConfiguration {
@Bean
public ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() {
return new ResourceChainResourceHandlerRegistrationCustomizer();
}
}
interface ResourceHandlerRegistrationCustomizer {
void customize(ResourceHandlerRegistration registration);
}
private static class ResourceChainResourceHandlerRegistrationCustomizer
implements ResourceHandlerRegistrationCustomizer {
@Autowired
private ResourceProperties resourceProperties = new ResourceProperties();
@Override
public void customize(ResourceHandlerRegistration registration) {
ResourceProperties.Chain properties = this.resourceProperties.getChain();
configureResourceChain(properties,
registration.resourceChain(properties.isCache()));
}
private void configureResourceChain(ResourceProperties.Chain properties,
ResourceChainRegistration chain) {
ResourceProperties.Strategy strategy = properties.getStrategy();
if (strategy.getFixed().isEnabled() || strategy.getContent().isEnabled()) {
chain.addResolver(getVersionResourceResolver(strategy));
}
if (properties.isGzipped()) {
chain.addResolver(new GzipResourceResolver());
}
if (properties.isHtmlApplicationCache()) {
chain.addTransformer(new AppCacheManifestTransformer());
}
}
private ResourceResolver getVersionResourceResolver(
ResourceProperties.Strategy properties) {
VersionResourceResolver resolver = new VersionResourceResolver();
if (properties.getFixed().isEnabled()) {
String version = properties.getFixed().getVersion();
String[] paths = properties.getFixed().getPaths();
resolver.addFixedVersionStrategy(version, paths);
}
if (properties.getContent().isEnabled()) {
String[] paths = properties.getContent().getPaths();
resolver.addContentVersionStrategy(paths);
}
return resolver;
}
}
}

@ -0,0 +1,113 @@
/*
* Copyright 2012-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.boot.autoconfigure.webflux;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.web.reactive.DispatcherHandler;
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.reactive.result.view.ViewResolver;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import org.springframework.web.server.session.WebSessionManager;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Functional WebFlux.
*
* @author Brian Clozel
*/
@Configuration
@ConditionalOnClass({DispatcherHandler.class, HttpHandler.class})
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnBean(RouterFunction.class)
@ConditionalOnMissingBean(HttpHandler.class)
@AutoConfigureAfter({ReactiveWebServerAutoConfiguration.class})
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebFluxFunctionalAutoConfiguration {
@Configuration
public static class WebFluxFunctionalConfig {
private final List<WebFilter> webFilters;
private final WebSessionManager webSessionManager;
private final List<HttpMessageReader> messageReaders;
private final List<HttpMessageWriter> messageWriters;
private final List<ViewResolver> viewResolvers;
public WebFluxFunctionalConfig(ObjectProvider<List<WebFilter>> webFilters,
ObjectProvider<WebSessionManager> webSessionManager,
ObjectProvider<List<HttpMessageReader>> messageReaders,
ObjectProvider<List<HttpMessageWriter>> messageWriters,
ObjectProvider<List<ViewResolver>> viewResolvers) {
this.webFilters = webFilters.getIfAvailable();
if (this.webFilters != null) {
AnnotationAwareOrderComparator.sort(this.webFilters);
}
this.webSessionManager = webSessionManager.getIfAvailable();
this.messageReaders = messageReaders.getIfAvailable();
this.messageWriters = messageWriters.getIfAvailable();
this.viewResolvers = viewResolvers.getIfAvailable();
}
@Bean
public HttpHandler httpHandler(List<RouterFunction> routerFunctions) {
Collections.sort(routerFunctions, new AnnotationAwareOrderComparator());
RouterFunction routerFunction = routerFunctions.stream().reduce(RouterFunction::and).get();
HandlerStrategies.Builder strategiesBuilder = HandlerStrategies.builder();
if (this.messageReaders != null) {
this.messageReaders.forEach(reader -> strategiesBuilder.messageReader(reader));
}
if (this.messageWriters != null) {
this.messageWriters.forEach(writer -> strategiesBuilder.messageWriter(writer));
}
if (this.viewResolvers != null) {
this.viewResolvers.forEach(viewResolver -> strategiesBuilder.viewResolver(viewResolver));
}
WebHandler webHandler = RouterFunctions.toHttpHandler(routerFunction, strategiesBuilder.build());
WebHttpHandlerBuilder builder = WebHttpHandlerBuilder
.webHandler(webHandler)
.sessionManager(this.webSessionManager);
if (this.webFilters != null) {
builder.filters(this.webFilters.toArray(new WebFilter[this.webFilters.size()]));
}
return builder.build();
}
}
}

@ -0,0 +1,42 @@
/*
* Copyright 2012-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.boot.autoconfigure.webflux;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* {@link ConfigurationProperties properties} for Spring WebFlux.
*
* @author Brian Clozel
* @since 2.0.0
*/
@ConfigurationProperties(prefix = "spring.webflux")
public class WebFluxProperties {
/**
* Path pattern used for static resources.
*/
private String staticPathPattern = "/**";
public String getStaticPathPattern() {
return this.staticPathPattern;
}
public void setStaticPathPattern(String staticPathPattern) {
this.staticPathPattern = staticPathPattern;
}
}

@ -113,6 +113,8 @@ org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\ org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.webflux.ReactiveWebServerAutoConfiguration,\ org.springframework.boot.autoconfigure.webflux.ReactiveWebServerAutoConfiguration,\
org.springframework.boot.autoconfigure.webflux.WebFluxAnnotationAutoConfiguration,\
org.springframework.boot.autoconfigure.webflux.WebFluxFunctionalAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

@ -0,0 +1,257 @@
/*
* Copyright 2012-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.boot.autoconfigure.webflux;
import org.junit.Test;
import org.springframework.boot.context.embedded.ReactiveWebApplicationContext;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
import org.springframework.web.reactive.config.WebFluxConfigurationSupport;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.resource.CachingResourceResolver;
import org.springframework.web.reactive.resource.CachingResourceTransformer;
import org.springframework.web.reactive.resource.PathResourceResolver;
import org.springframework.web.reactive.resource.ResourceWebHandler;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
import org.springframework.web.server.handler.FilteringWebHandler;
import org.springframework.web.server.handler.WebHandlerDecorator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link WebFluxAnnotationAutoConfiguration}.
*
* @author Brian Clozel
*/
public class WebFluxAnnotationAutoConfigurationTests {
private ReactiveWebApplicationContext context;
@Test
public void shouldNotProcessIfExistingHttpHandler() throws Exception {
load(CustomHttpHandler.class);
assertThat(this.context.getBeansOfType(RequestMappingHandlerMapping.class).size()).isEqualTo(0);
assertThat(this.context.getBeansOfType(RequestMappingHandlerAdapter.class).size()).isEqualTo(0);
assertThat(this.context.getBeansOfType(HttpWebHandlerAdapter.class).size()).isEqualTo(0);
}
@Test
public void shouldNotProcessIfExistingWebReactiveConfiguration() throws Exception {
load(WebFluxConfigurationSupport.class);
assertThat(this.context.getBeansOfType(RequestMappingHandlerMapping.class).size()).isEqualTo(1);
assertThat(this.context.getBeansOfType(RequestMappingHandlerAdapter.class).size()).isEqualTo(1);
}
@Test
public void shouldCreateDefaultBeans() throws Exception {
load(BaseConfiguration.class);
assertThat(this.context.getBeansOfType(RequestMappingHandlerMapping.class).size()).isEqualTo(1);
assertThat(this.context.getBeansOfType(RequestMappingHandlerAdapter.class).size()).isEqualTo(1);
assertThat(this.context.getBeansOfType(CompositeContentTypeResolver.class).size()).isEqualTo(1);
assertThat(this.context.getBean("resourceHandlerMapping", HandlerMapping.class)).isNotNull();
}
@Test
public void shouldRegisterCustomHandlerMethodArgumentResolver() throws Exception {
load(CustomArgumentResolvers.class);
RequestMappingHandlerAdapter adapter = this.context.getBean(RequestMappingHandlerAdapter.class);
assertThat(adapter.getArgumentResolvers())
.contains(this.context.getBean("firstResolver", HandlerMethodArgumentResolver.class),
this.context.getBean("secondResolver", HandlerMethodArgumentResolver.class));
}
@Test
public void shouldRegisterCustomWebFilters() throws Exception {
load(CustomWebFilters.class);
HttpHandler handler = this.context.getBean(HttpHandler.class);
assertThat(handler).isInstanceOf(WebHandler.class);
WebHandler webHandler = (WebHandler) handler;
while (webHandler instanceof WebHandlerDecorator) {
if (webHandler instanceof FilteringWebHandler) {
FilteringWebHandler filteringWebHandler = (FilteringWebHandler) webHandler;
assertThat(filteringWebHandler.getFilters()).containsExactly(
this.context.getBean("firstWebFilter", WebFilter.class),
this.context.getBean("aWebFilter", WebFilter.class),
this.context.getBean("lastWebFilter", WebFilter.class));
return;
}
webHandler = ((WebHandlerDecorator) webHandler).getDelegate();
}
fail("Did not find any FilteringWebHandler");
}
@Test
public void shouldRegisterResourceHandlerMapping() throws Exception {
load(BaseConfiguration.class);
SimpleUrlHandlerMapping hm = this.context.getBean("resourceHandlerMapping", SimpleUrlHandlerMapping.class);
assertThat(hm.getUrlMap().get("/**")).isInstanceOf(ResourceWebHandler.class);
ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap().get("/**");
assertThat(staticHandler.getLocations()).hasSize(5);
assertThat(hm.getUrlMap().get("/webjars/**")).isInstanceOf(ResourceWebHandler.class);
ResourceWebHandler webjarsHandler = (ResourceWebHandler) hm.getUrlMap().get("/webjars/**");
assertThat(webjarsHandler.getLocations()).hasSize(1);
assertThat(webjarsHandler.getLocations().get(0))
.isEqualTo(new ClassPathResource("/META-INF/resources/webjars/"));
}
@Test
public void shouldMapResourcesToCustomPath() throws Exception {
load(BaseConfiguration.class, "spring.webflux.static-path-pattern:/static/**");
SimpleUrlHandlerMapping hm = this.context.getBean("resourceHandlerMapping", SimpleUrlHandlerMapping.class);
assertThat(hm.getUrlMap().get("/static/**")).isInstanceOf(ResourceWebHandler.class);
ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap().get("/static/**");
assertThat(staticHandler.getLocations()).hasSize(5);
}
@Test
public void shouldNotMapResourcesWhenDisabled() throws Exception {
load(BaseConfiguration.class, "spring.resources.add-mappings:false");
assertThat(this.context.getBean("resourceHandlerMapping")).isNotInstanceOf(SimpleUrlHandlerMapping.class);
}
@Test
public void resourceHandlerChainEnabled() throws Exception {
load(BaseConfiguration.class, "spring.resources.chain.enabled:true");
SimpleUrlHandlerMapping hm = this.context.getBean("resourceHandlerMapping", SimpleUrlHandlerMapping.class);
assertThat(hm.getUrlMap().get("/**")).isInstanceOf(ResourceWebHandler.class);
ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap().get("/**");
assertThat(staticHandler.getResourceResolvers()).extractingResultOf("getClass")
.containsOnly(CachingResourceResolver.class, PathResourceResolver.class);
assertThat(staticHandler.getResourceTransformers()).extractingResultOf("getClass")
.containsOnly(CachingResourceTransformer.class);
}
@Test
public void shouldRegisterViewResolvers() throws Exception {
load(ViewResolvers.class);
ViewResolutionResultHandler resultHandler = this.context.getBean(ViewResolutionResultHandler.class);
assertThat(resultHandler.getViewResolvers()).containsExactly(
this.context.getBean("aViewResolver", ViewResolver.class),
this.context.getBean("anotherViewResolver", ViewResolver.class)
);
}
private void load(Class<?> config, String... environment) {
this.context = new ReactiveWebApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context, environment);
this.context.register(config);
if (!config.equals(BaseConfiguration.class)) {
this.context.register(BaseConfiguration.class);
}
this.context.refresh();
}
@Configuration
protected static class CustomWebFilters {
@Bean
public WebFilter aWebFilter() {
return mock(WebFilter.class);
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
public WebFilter lastWebFilter() {
return mock(WebFilter.class);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public WebFilter firstWebFilter() {
return mock(WebFilter.class);
}
}
@Configuration
protected static class CustomArgumentResolvers {
@Bean
public HandlerMethodArgumentResolver firstResolver() {
return mock(HandlerMethodArgumentResolver.class);
}
@Bean
public HandlerMethodArgumentResolver secondResolver() {
return mock(HandlerMethodArgumentResolver.class);
}
}
@Configuration
protected static class ViewResolvers {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ViewResolver aViewResolver() {
return mock(ViewResolver.class);
}
@Bean
public ViewResolver anotherViewResolver() {
return mock(ViewResolver.class);
}
}
@Configuration
@Import({WebFluxAnnotationAutoConfiguration.class})
@EnableConfigurationProperties(WebFluxProperties.class)
protected static class BaseConfiguration {
@Bean
public MockReactiveWebServerFactory mockReactiveWebServerFactory() {
return new MockReactiveWebServerFactory();
}
}
@Configuration
protected static class CustomHttpHandler {
@Bean
public HttpHandler httpHandler() {
return (serverHttpRequest, serverHttpResponse) -> null;
}
}
}

@ -0,0 +1,153 @@
/*
* Copyright 2012-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.boot.autoconfigure.webflux;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.context.embedded.ReactiveWebApplicationContext;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.server.reactive.HttpHandler;
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.server.WebFilter;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
import org.springframework.web.server.handler.FilteringWebHandler;
import org.springframework.web.server.handler.WebHandlerDecorator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
/**
* Tests for {@link WebFluxFunctionalAutoConfiguration}.
*
* @author Brian Clozel
*/
public class WebFluxFunctionalAutoConfigurationTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private ReactiveWebApplicationContext context;
@Test
public void shouldNotProcessIfExistingHttpHandler() throws Exception {
load(CustomHttpHandler.class);
assertThat(this.context.getBeansOfType(HttpWebHandlerAdapter.class).size()).isEqualTo(0);
}
@Test
public void shouldFailIfNoHttpHandler() throws Exception {
this.thrown.expect(ApplicationContextException.class);
this.thrown.expectMessage("Unable to start ReactiveWebApplicationContext due to missing HttpHandler bean.");
load(BaseConfiguration.class);
}
@Test
public void shouldConfigureHttpHandler() {
load(FunctionalConfig.class);
assertThat(this.context.getBeansOfType(HttpHandler.class).size()).isEqualTo(1);
}
@Test
public void shouldConfigureWebFilters() {
load(FunctionalConfigWithWebFilters.class);
assertThat(this.context.getBeansOfType(HttpHandler.class).size()).isEqualTo(1);
HttpHandler handler = this.context.getBean(HttpHandler.class);
assertThat(handler).isInstanceOf(WebHandler.class);
WebHandler webHandler = (WebHandler) handler;
while (webHandler instanceof WebHandlerDecorator) {
if (webHandler instanceof FilteringWebHandler) {
FilteringWebHandler filteringWebHandler = (FilteringWebHandler) webHandler;
assertThat(filteringWebHandler.getFilters()).containsExactly(
this.context.getBean("customWebFilter", WebFilter.class));
return;
}
webHandler = ((WebHandlerDecorator) webHandler).getDelegate();
}
fail("Did not find any FilteringWebHandler");
}
private void load(Class<?> config, String... environment) {
this.context = new ReactiveWebApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context, environment);
this.context.register(config);
if (!config.equals(BaseConfiguration.class)) {
this.context.register(BaseConfiguration.class);
}
this.context.refresh();
}
@Configuration
@Import({WebFluxFunctionalAutoConfiguration.class})
@EnableConfigurationProperties(WebFluxProperties.class)
protected static class BaseConfiguration {
@Bean
public MockReactiveWebServerFactory mockReactiveWebServerFactory() {
return new MockReactiveWebServerFactory();
}
}
@Configuration
protected static class FunctionalConfig {
@Bean
public RouterFunction routerFunction() {
return RouterFunctions.route(RequestPredicates.GET("/test"), serverRequest -> null);
}
}
@Configuration
protected static class FunctionalConfigWithWebFilters {
@Bean
public RouterFunction routerFunction() {
return RouterFunctions.route(RequestPredicates.GET("/test"), serverRequest -> null);
}
@Bean
public WebFilter customWebFilter() {
return (serverWebExchange, webFilterChain) -> null;
}
}
@Configuration
protected static class CustomHttpHandler {
@Bean
public HttpHandler httpHandler() {
return (serverHttpRequest, serverHttpResponse) -> null;
}
@Bean
public RouterFunction routerFunction() {
return RouterFunctions.route(RequestPredicates.GET("/test"), serverRequest -> null);
}
}
}
Loading…
Cancel
Save