diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration.java index 3004fdb91f4..ba059641d0e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -16,6 +16,9 @@ package org.springframework.boot.autoconfigure.web.reactive; +import java.util.Collections; +import java.util.Map; + import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -26,7 +29,9 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; +import org.springframework.http.server.reactive.ContextPathCompositeHandler; import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.util.StringUtils; import org.springframework.web.reactive.DispatcherHandler; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; @@ -55,8 +60,13 @@ public class HttpHandlerAutoConfiguration { } @Bean - public HttpHandler httpHandler() { - return WebHttpHandlerBuilder.applicationContext(this.applicationContext).build(); + public HttpHandler httpHandler(WebFluxProperties properties) { + HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(this.applicationContext).build(); + if (StringUtils.hasText(properties.getBasePath())) { + Map handlersMap = Collections.singletonMap(properties.getBasePath(), httpHandler); + return new ContextPathCompositeHandler(handlersMap); + } + return httpHandler; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java index 707ca1cf67e..aafa45e07c0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.web.reactive; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.util.StringUtils; /** * {@link ConfigurationProperties properties} for Spring WebFlux. @@ -27,6 +28,11 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "spring.webflux") public class WebFluxProperties { + /** + * Base path for all web handlers. + */ + private String basePath; + /** * Date format to use. For instance, `dd/MM/yyyy`. */ @@ -37,6 +43,27 @@ public class WebFluxProperties { */ private String staticPathPattern = "/**"; + public String getBasePath() { + return basePath; + } + + public void setBasePath(String basePath) { + this.basePath = cleanBasePath(basePath); + } + + private String cleanBasePath(String basePath) { + String candidate = StringUtils.trimWhitespace(basePath); + if (StringUtils.hasText(candidate)) { + if (!candidate.startsWith("/")) { + candidate = "/" + candidate; + } + if (candidate.endsWith("/")) { + candidate = candidate.substring(0, candidate.length() - 1); + } + } + return candidate; + } + public String getDateFormat() { return this.dateFormat; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfigurationTests.java index a3f97f777a5..81845b44056 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -16,12 +16,14 @@ package org.springframework.boot.autoconfigure.web.reactive; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.server.reactive.ContextPathCompositeHandler; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerResponse; @@ -56,6 +58,18 @@ class HttpHandlerAutoConfigurationTests { .run((context) -> assertThat(context).hasSingleBean(HttpHandler.class)); } + @Test + void shouldConfigureBasePathCompositeHandler() { + this.contextRunner.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class)) + .withPropertyValues("spring.webflux.base-path=/something").run((context) -> { + assertThat(context).hasSingleBean(HttpHandler.class); + HttpHandler httpHandler = context.getBean(HttpHandler.class); + assertThat(httpHandler).isInstanceOf(ContextPathCompositeHandler.class) + .extracting("handlerMap", InstanceOfAssertFactories.map(String.class, HttpHandler.class)) + .containsKey("/something"); + }); + } + @Configuration(proxyBeanMethods = false) static class CustomHttpHandler { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxPropertiesTests.java new file mode 100644 index 00000000000..83eb6b20b78 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxPropertiesTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.web.reactive; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.context.properties.source.ConfigurationPropertySource; +import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link WebFluxProperties} + * + * @author Brian Clozel + */ +class WebFluxPropertiesTests { + + private final WebFluxProperties properties = new WebFluxProperties(); + + @Test + void shouldPrefixBasePathWithMissingSlash() { + bind("spring.webflux.base-path", "something"); + assertThat(this.properties.getBasePath()).isEqualTo("/something"); + } + + @Test + void shouldRemoveTrailingSlashFromBasePath() { + bind("spring.webflux.base-path", "/something/"); + assertThat(this.properties.getBasePath()).isEqualTo("/something"); + } + + private void bind(String name, String value) { + bind(Collections.singletonMap(name, value)); + } + + private void bind(Map map) { + ConfigurationPropertySource source = new MapConfigurationPropertySource(map); + new Binder(source).bind("spring.webflux", Bindable.ofInstance(this.properties)); + } + +}