Commit 8317977e authored by Brian Clozel's avatar Brian Clozel

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
parent 656b509f
...@@ -354,6 +354,11 @@ ...@@ -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>
......
/*
* 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();
}
}
}
/*
* 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,\ ...@@ -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
......
/*
* 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);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment