Browse Source
This commit also introduces integration tests to test the status quo regarding encoding. Closes gh-33071pull/33350/head
17 changed files with 681 additions and 280 deletions
@ -0,0 +1,207 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2024 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.web.reactive.config; |
||||||
|
|
||||||
|
import java.time.Duration; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import freemarker.cache.ClassTemplateLoader; |
||||||
|
import org.junit.jupiter.api.Nested; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import reactor.test.StepVerifier; |
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.context.ApplicationContext; |
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.stereotype.Controller; |
||||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||||
|
import org.springframework.web.reactive.DispatcherHandler; |
||||||
|
import org.springframework.web.reactive.result.view.freemarker.FreeMarkerConfigurer; |
||||||
|
import org.springframework.web.reactive.result.view.freemarker.FreeMarkerViewResolver; |
||||||
|
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest; |
||||||
|
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpResponse; |
||||||
|
import org.springframework.web.testfixture.server.MockServerWebExchange; |
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.ISO_8859_1; |
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8; |
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatRuntimeException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Integration tests for view resolution with {@code @EnableWebFlux}. |
||||||
|
* |
||||||
|
* @author Sam Brannen |
||||||
|
* @since 6.1.11 |
||||||
|
* @see org.springframework.web.servlet.config.annotation.ViewResolutionIntegrationTests |
||||||
|
*/ |
||||||
|
class WebFluxViewResolutionIntegrationTests { |
||||||
|
|
||||||
|
private static final MediaType TEXT_HTML_UTF8 = MediaType.parseMediaType("text/html;charset=UTF-8"); |
||||||
|
|
||||||
|
private static final MediaType TEXT_HTML_ISO_8859_1 = MediaType.parseMediaType("text/html;charset=ISO-8859-1"); |
||||||
|
|
||||||
|
private static final String EXPECTED_BODY = "<html><body>Hello, Java Café</body></html>"; |
||||||
|
|
||||||
|
|
||||||
|
@Nested |
||||||
|
class FreeMarkerTests { |
||||||
|
|
||||||
|
private static final ClassTemplateLoader classTemplateLoader = |
||||||
|
new ClassTemplateLoader(WebFluxViewResolutionIntegrationTests.class, ""); |
||||||
|
|
||||||
|
@Test |
||||||
|
void freemarkerWithInvalidConfig() { |
||||||
|
assertThatRuntimeException() |
||||||
|
.isThrownBy(() -> runTest(InvalidFreeMarkerWebFluxConfig.class)) |
||||||
|
.withMessageContaining("In addition to a FreeMarker view resolver "); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void freemarkerWithDefaults() throws Exception { |
||||||
|
MockServerHttpResponse response = runTest(FreeMarkerWebFluxConfig.class); |
||||||
|
StepVerifier.create(response.getBodyAsString()).expectNext(EXPECTED_BODY).expectComplete().verify(); |
||||||
|
assertThat(response.getHeaders().getContentType()).isEqualTo(TEXT_HTML_UTF8); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void freemarkerWithExplicitDefaultEncoding() throws Exception { |
||||||
|
MockServerHttpResponse response = runTest(ExplicitDefaultEncodingConfig.class); |
||||||
|
StepVerifier.create(response.getBodyAsString()).expectNext(EXPECTED_BODY).expectComplete().verify(); |
||||||
|
assertThat(response.getHeaders().getContentType()).isEqualTo(TEXT_HTML_UTF8); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void freemarkerWithExplicitDefaultEncodingAndContentType() throws Exception { |
||||||
|
MockServerHttpResponse response = runTest(ExplicitDefaultEncodingAndContentTypeConfig.class); |
||||||
|
StepVerifier.create(response.getBodyAsString()).expectNext(EXPECTED_BODY).expectComplete().verify(); |
||||||
|
// When the Content-Type (supported media type) is explicitly set on the view resolver, it should be used.
|
||||||
|
assertThat(response.getHeaders().getContentType()).isEqualTo(TEXT_HTML_ISO_8859_1); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@EnableWebFlux |
||||||
|
@Configuration(proxyBeanMethods = false) |
||||||
|
static class InvalidFreeMarkerWebFluxConfig implements WebFluxConfigurer { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void configureViewResolvers(ViewResolverRegistry registry) { |
||||||
|
registry.freeMarker(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false) |
||||||
|
static class FreeMarkerWebFluxConfig extends AbstractWebFluxConfig { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void configureViewResolvers(ViewResolverRegistry registry) { |
||||||
|
registry.freeMarker(); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
public FreeMarkerConfigurer freeMarkerConfigurer() { |
||||||
|
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); |
||||||
|
configurer.setPreTemplateLoaders(classTemplateLoader); |
||||||
|
return configurer; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false) |
||||||
|
static class ExplicitDefaultEncodingConfig extends AbstractWebFluxConfig { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void configureViewResolvers(ViewResolverRegistry registry) { |
||||||
|
registry.freeMarker(); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
public FreeMarkerConfigurer freeMarkerConfigurer() { |
||||||
|
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); |
||||||
|
configurer.setPreTemplateLoaders(classTemplateLoader); |
||||||
|
configurer.setDefaultEncoding(UTF_8.name()); |
||||||
|
return configurer; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false) |
||||||
|
static class ExplicitDefaultEncodingAndContentTypeConfig extends AbstractWebFluxConfig { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
ApplicationContext applicationContext; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void configureViewResolvers(ViewResolverRegistry registry) { |
||||||
|
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver("", ".ftl"); |
||||||
|
resolver.setSupportedMediaTypes(List.of(TEXT_HTML_ISO_8859_1)); |
||||||
|
resolver.setApplicationContext(this.applicationContext); |
||||||
|
registry.viewResolver(resolver); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
public FreeMarkerConfigurer freeMarkerConfigurer() { |
||||||
|
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); |
||||||
|
configurer.setPreTemplateLoaders(classTemplateLoader); |
||||||
|
configurer.setDefaultEncoding(ISO_8859_1.name()); |
||||||
|
return configurer; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@Bean |
||||||
|
public SampleController sampleController() { |
||||||
|
return new SampleController("index_ISO-8859-1"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private static MockServerHttpResponse runTest(Class<?> configClass) throws Exception { |
||||||
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configClass); |
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/")); |
||||||
|
new DispatcherHandler(context).handle(exchange).block(Duration.ofSeconds(1)); |
||||||
|
return exchange.getResponse(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@EnableWebFlux |
||||||
|
abstract static class AbstractWebFluxConfig implements WebFluxConfigurer { |
||||||
|
|
||||||
|
@Bean |
||||||
|
public SampleController sampleController() { |
||||||
|
return new SampleController("index_UTF-8"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Controller |
||||||
|
static class SampleController { |
||||||
|
|
||||||
|
private final String viewName; |
||||||
|
|
||||||
|
SampleController(String viewName) { |
||||||
|
this.viewName = viewName; |
||||||
|
} |
||||||
|
|
||||||
|
@GetMapping("/") |
||||||
|
String index(Map<String, Object> model) { |
||||||
|
model.put("hello", "Hello"); |
||||||
|
return this.viewName; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1 @@ |
|||||||
|
<html><body>${hello}, Java Café</body></html> |
||||||
@ -0,0 +1 @@ |
|||||||
|
<html><body>${hello}, Java Café</body></html> |
||||||
Loading…
Reference in new issue