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-8386pull/8410/head
parent
656b509f03
commit
8317977e1b
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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…
Reference in New Issue