-
Notifications
You must be signed in to change notification settings - Fork 40.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add welcome page support for Spring WebFlux
This commit adds the support for static and templated welcome pages with Spring WebFlux. The implementation is backed by a `RouterFunction` that's serving a static `index.html` file or rendering an `index` view. Closes gh-9785
- Loading branch information
Showing
5 changed files
with
317 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
...org/springframework/boot/autoconfigure/web/reactive/WelcomePageRouterFunctionFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* | ||
* 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.Arrays; | ||
|
||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; | ||
import org.springframework.context.ApplicationContext; | ||
import org.springframework.core.io.Resource; | ||
import org.springframework.core.io.ResourceLoader; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.web.reactive.function.server.RouterFunction; | ||
import org.springframework.web.reactive.function.server.RouterFunctions; | ||
import org.springframework.web.reactive.function.server.ServerResponse; | ||
|
||
import static org.springframework.web.reactive.function.server.RequestPredicates.GET; | ||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept; | ||
|
||
/** | ||
* A {@link RouterFunction} factory for an application's welcome page. Supports both | ||
* static and templated files. If both a static and templated index page are available, | ||
* the static page is preferred. | ||
* | ||
* @author Brian Clozel | ||
*/ | ||
final class WelcomePageRouterFunctionFactory { | ||
|
||
private final String staticPathPattern; | ||
|
||
private final Resource welcomePage; | ||
|
||
private final boolean welcomePageTemplateExists; | ||
|
||
WelcomePageRouterFunctionFactory(TemplateAvailabilityProviders templateAvailabilityProviders, | ||
ApplicationContext applicationContext, String[] staticLocations, String staticPathPattern) { | ||
this.staticPathPattern = staticPathPattern; | ||
this.welcomePage = getWelcomePage(applicationContext, staticLocations); | ||
this.welcomePageTemplateExists = welcomeTemplateExists(templateAvailabilityProviders, applicationContext); | ||
} | ||
|
||
private Resource getWelcomePage(ResourceLoader resourceLoader, String[] staticLocations) { | ||
return Arrays.stream(staticLocations).map((location) -> getIndexHtml(resourceLoader, location)) | ||
.filter(this::isReadable).findFirst().orElse(null); | ||
} | ||
|
||
private Resource getIndexHtml(ResourceLoader resourceLoader, String location) { | ||
return resourceLoader.getResource(location + "index.html"); | ||
} | ||
|
||
private boolean isReadable(Resource resource) { | ||
try { | ||
return resource.exists() && (resource.getURL() != null); | ||
} | ||
catch (Exception ex) { | ||
return false; | ||
} | ||
} | ||
|
||
private boolean welcomeTemplateExists(TemplateAvailabilityProviders templateAvailabilityProviders, | ||
ApplicationContext applicationContext) { | ||
return templateAvailabilityProviders.getProvider("index", applicationContext) != null; | ||
} | ||
|
||
RouterFunction<ServerResponse> createRouterFunction() { | ||
if (this.welcomePage != null && "/**".equals(this.staticPathPattern)) { | ||
return RouterFunctions.route(GET("/").and(accept(MediaType.TEXT_HTML)), | ||
(req) -> ServerResponse.ok().contentType(MediaType.TEXT_HTML).bodyValue(this.welcomePage)); | ||
} | ||
else if (this.welcomePageTemplateExists) { | ||
return RouterFunctions.route(GET("/").and(accept(MediaType.TEXT_HTML)), | ||
(req) -> ServerResponse.ok().render("index")); | ||
} | ||
return null; | ||
} | ||
|
||
} |
200 changes: 200 additions & 0 deletions
200
...pringframework/boot/autoconfigure/web/reactive/WelcomePageRouterFunctionFactoryTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
/* | ||
* 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.nio.charset.StandardCharsets; | ||
import java.util.Collections; | ||
import java.util.Locale; | ||
import java.util.Map; | ||
|
||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import reactor.core.publisher.Mono; | ||
|
||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider; | ||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; | ||
import org.springframework.context.support.StaticApplicationContext; | ||
import org.springframework.core.io.buffer.DataBuffer; | ||
import org.springframework.core.io.buffer.DataBufferFactory; | ||
import org.springframework.core.io.buffer.DefaultDataBufferFactory; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.test.web.reactive.server.WebTestClient; | ||
import org.springframework.web.reactive.function.server.HandlerStrategies; | ||
import org.springframework.web.reactive.result.view.View; | ||
import org.springframework.web.reactive.result.view.ViewResolver; | ||
import org.springframework.web.server.ServerWebExchange; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
/** | ||
* Tests for {@link WelcomePageRouterFunctionFactory} | ||
* | ||
* @author Brian Clozel | ||
*/ | ||
class WelcomePageRouterFunctionFactoryTests { | ||
|
||
private StaticApplicationContext applicationContext; | ||
|
||
private final String[] noIndexLocations = { "classpath:/" }; | ||
|
||
private final String[] indexLocations = { "classpath:/public/", "classpath:/welcome-page/" }; | ||
|
||
@BeforeEach | ||
void setup() { | ||
this.applicationContext = new StaticApplicationContext(); | ||
this.applicationContext.refresh(); | ||
} | ||
|
||
@Test | ||
void handlesRequestForStaticPageThatAcceptsTextHtml() { | ||
WebTestClient client = withStaticIndex(); | ||
client.get().uri("/").accept(MediaType.TEXT_HTML).exchange().expectStatus().isOk().expectBody(String.class) | ||
.isEqualTo("welcome-page-static"); | ||
} | ||
|
||
@Test | ||
void handlesRequestForStaticPageThatAcceptsAll() { | ||
WebTestClient client = withStaticIndex(); | ||
client.get().uri("/").accept(MediaType.ALL).exchange().expectStatus().isOk().expectBody(String.class) | ||
.isEqualTo("welcome-page-static"); | ||
} | ||
|
||
@Test | ||
void doesNotHandleRequestThatDoesNotAcceptTextHtml() { | ||
WebTestClient client = withStaticIndex(); | ||
client.get().uri("/").accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isNotFound(); | ||
} | ||
|
||
@Test | ||
void handlesRequestWithNoAcceptHeader() { | ||
WebTestClient client = withStaticIndex(); | ||
client.get().uri("/").exchange().expectStatus().isOk().expectBody(String.class) | ||
.isEqualTo("welcome-page-static"); | ||
} | ||
|
||
@Test | ||
void handlesRequestWithEmptyAcceptHeader() { | ||
WebTestClient client = withStaticIndex(); | ||
client.get().uri("/").header(HttpHeaders.ACCEPT, "").exchange().expectStatus().isOk().expectBody(String.class) | ||
.isEqualTo("welcome-page-static"); | ||
} | ||
|
||
@Test | ||
void producesNotFoundResponseWhenThereIsNoWelcomePage() { | ||
WelcomePageRouterFunctionFactory factory = factoryWithoutTemplateSupport(this.noIndexLocations, "/**"); | ||
assertThat(factory.createRouterFunction()).isNull(); | ||
} | ||
|
||
@Test | ||
void handlesRequestForTemplateThatAcceptsTextHtml() { | ||
WebTestClient client = withTemplateIndex(); | ||
client.get().uri("/").accept(MediaType.TEXT_HTML).exchange().expectStatus().isOk().expectBody(String.class) | ||
.isEqualTo("welcome-page-template"); | ||
} | ||
|
||
@Test | ||
void handlesRequestForTemplateThatAcceptsAll() { | ||
WebTestClient client = withTemplateIndex(); | ||
client.get().uri("/").accept(MediaType.ALL).exchange().expectStatus().isOk().expectBody(String.class) | ||
.isEqualTo("welcome-page-template"); | ||
} | ||
|
||
@Test | ||
void prefersAStaticResourceToATemplate() { | ||
WebTestClient client = withStaticAndTemplateIndex(); | ||
client.get().uri("/").accept(MediaType.ALL).exchange().expectStatus().isOk().expectBody(String.class) | ||
.isEqualTo("welcome-page-static"); | ||
} | ||
|
||
private WebTestClient createClient(WelcomePageRouterFunctionFactory factory) { | ||
return WebTestClient.bindToRouterFunction(factory.createRouterFunction()).build(); | ||
} | ||
|
||
private WebTestClient createClient(WelcomePageRouterFunctionFactory factory, ViewResolver viewResolver) { | ||
return WebTestClient.bindToRouterFunction(factory.createRouterFunction()) | ||
.handlerStrategies(HandlerStrategies.builder().viewResolver(viewResolver).build()).build(); | ||
} | ||
|
||
private WebTestClient withStaticIndex() { | ||
WelcomePageRouterFunctionFactory factory = factoryWithoutTemplateSupport(this.indexLocations, "/**"); | ||
return WebTestClient.bindToRouterFunction(factory.createRouterFunction()).build(); | ||
} | ||
|
||
private WebTestClient withTemplateIndex() { | ||
WelcomePageRouterFunctionFactory factory = factoryWithTemplateSupport(this.noIndexLocations); | ||
TestViewResolver testViewResolver = new TestViewResolver(); | ||
return WebTestClient.bindToRouterFunction(factory.createRouterFunction()) | ||
.handlerStrategies(HandlerStrategies.builder().viewResolver(testViewResolver).build()).build(); | ||
} | ||
|
||
private WebTestClient withStaticAndTemplateIndex() { | ||
WelcomePageRouterFunctionFactory factory = factoryWithTemplateSupport(this.indexLocations); | ||
TestViewResolver testViewResolver = new TestViewResolver(); | ||
return WebTestClient.bindToRouterFunction(factory.createRouterFunction()) | ||
.handlerStrategies(HandlerStrategies.builder().viewResolver(testViewResolver).build()).build(); | ||
} | ||
|
||
private WelcomePageRouterFunctionFactory factoryWithoutTemplateSupport(String[] locations, | ||
String staticPathPattern) { | ||
return new WelcomePageRouterFunctionFactory(new TestTemplateAvailabilityProviders(), this.applicationContext, | ||
locations, staticPathPattern); | ||
} | ||
|
||
private WelcomePageRouterFunctionFactory factoryWithTemplateSupport(String[] locations) { | ||
return new WelcomePageRouterFunctionFactory(new TestTemplateAvailabilityProviders("index"), | ||
this.applicationContext, locations, "/**"); | ||
} | ||
|
||
static class TestTemplateAvailabilityProviders extends TemplateAvailabilityProviders { | ||
|
||
TestTemplateAvailabilityProviders() { | ||
super(Collections.emptyList()); | ||
} | ||
|
||
TestTemplateAvailabilityProviders(String viewName) { | ||
this((view, environment, classLoader, resourceLoader) -> view.equals(viewName)); | ||
} | ||
|
||
TestTemplateAvailabilityProviders(TemplateAvailabilityProvider provider) { | ||
super(Collections.singletonList(provider)); | ||
} | ||
|
||
} | ||
|
||
static class TestViewResolver implements ViewResolver { | ||
|
||
@Override | ||
public Mono<View> resolveViewName(String viewName, Locale locale) { | ||
return Mono.just(new TestView()); | ||
} | ||
|
||
} | ||
|
||
static class TestView implements View { | ||
|
||
private final DataBufferFactory bufferFactory = new DefaultDataBufferFactory(); | ||
|
||
@Override | ||
public Mono<Void> render(Map<String, ?> model, MediaType contentType, ServerWebExchange exchange) { | ||
DataBuffer buffer = this.bufferFactory.wrap("welcome-page-template".getBytes(StandardCharsets.UTF_8)); | ||
return exchange.getResponse().writeWith(Mono.just(buffer)); | ||
} | ||
|
||
} | ||
|
||
} |
2 changes: 1 addition & 1 deletion
2
spring-boot-project/spring-boot-autoconfigure/src/test/resources/welcome-page/index.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
|
||
welcome-page-static |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters