From 5f941c1dd17e11fd5704fe8a12038eb41b5704c5 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Mon, 5 Sep 2016 12:31:00 +0200 Subject: [PATCH] Fix using system default charset in view rendering Prior to this commit, FreeMarkerView used the system default charset to render. This commit switches this by defaulting to UTF-8, if no charset is specified in the content type. - Add contentType parameter to AbstractView.renderInternal, used to determine the charset contained therein - Adds a defaultCharset property to AbstractView and ViewResolverSupport. --- .../reactive/result/view/AbstractView.java | 31 ++++++++++++++++--- .../result/view/UrlBasedViewResolver.java | 2 ++ .../result/view/ViewResolverSupport.java | 23 ++++++++++++++ .../view/freemarker/FreeMarkerView.java | 15 +++++++-- .../view/UrlBasedViewResolverTests.java | 5 ++- 5 files changed, 68 insertions(+), 8 deletions(-) diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java index fe39b75f76..e286bf14be 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java @@ -16,6 +16,8 @@ package org.springframework.web.reactive.result.view; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -45,6 +47,8 @@ public abstract class AbstractView implements View, ApplicationContextAware { private final List mediaTypes = new ArrayList<>(4); + private Charset defaultCharset = StandardCharsets.UTF_8; + private ApplicationContext applicationContext; @@ -73,6 +77,24 @@ public abstract class AbstractView implements View, ApplicationContextAware { return this.mediaTypes; } + /** + * Set the default charset for this view, used when the + * {@linkplain #setSupportedMediaTypes(List) content type} does not contain one. + * Default is {@linkplain StandardCharsets#UTF_8 UTF 8}. + */ + public void setDefaultCharset(Charset defaultCharset) { + Assert.notNull(defaultCharset, "'defaultCharset' must not be null"); + this.defaultCharset = defaultCharset; + } + + /** + * Return the default charset, used when the + * {@linkplain #setSupportedMediaTypes(List) content type} does not contain one. + */ + public Charset getDefaultCharset() { + return this.defaultCharset; + } + @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; @@ -104,7 +126,7 @@ public abstract class AbstractView implements View, ApplicationContextAware { } Map mergedModel = getModelAttributes(model, exchange); - return renderInternal(mergedModel, exchange); + return renderInternal(mergedModel, contentType, exchange); } /** @@ -127,11 +149,12 @@ public abstract class AbstractView implements View, ApplicationContextAware { * Subclasses must implement this method to actually render the view. * @param renderAttributes combined output Map (never {@code null}), * with dynamic values taking precedence over static attributes - * @param exchange current exchange - * @return {@code Mono} to represent when and if rendering succeeds + * @param contentType the content type selected to render with which should + * match one of the {@link #getSupportedMediaTypes() supported media types}. + *@param exchange current exchange @return {@code Mono} to represent when and if rendering succeeds */ protected abstract Mono renderInternal(Map renderAttributes, - ServerWebExchange exchange); + MediaType contentType, ServerWebExchange exchange); @Override diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/UrlBasedViewResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/UrlBasedViewResolver.java index 5a60a56745..2836819099 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/UrlBasedViewResolver.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/UrlBasedViewResolver.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.reactive.result.view; import java.util.Locale; @@ -194,6 +195,7 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes protected AbstractUrlBasedView createUrlBasedView(String viewName) { AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass()); view.setSupportedMediaTypes(getSupportedMediaTypes()); + view.setDefaultCharset(getDefaultCharset()); view.setUrl(getPrefix() + viewName + getSuffix()); return view; } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolverSupport.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolverSupport.java index c9379e28a3..5fec6c1704 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolverSupport.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolverSupport.java @@ -16,6 +16,8 @@ package org.springframework.web.reactive.result.view; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -38,6 +40,8 @@ public abstract class ViewResolverSupport implements ApplicationContextAware, Or private List mediaTypes = new ArrayList<>(4); + private Charset defaultCharset = StandardCharsets.UTF_8; + private ApplicationContext applicationContext; private int order = Integer.MAX_VALUE; @@ -67,6 +71,25 @@ public abstract class ViewResolverSupport implements ApplicationContextAware, Or return this.mediaTypes; } + /** + * Set the default charset for this view, used when the + * {@linkplain #setSupportedMediaTypes(List) content type} does not contain one. + * Default is {@linkplain StandardCharsets#UTF_8 UTF 8}. + */ + public void setDefaultCharset(Charset defaultCharset) { + Assert.notNull(defaultCharset, "'defaultCharset' must not be null"); + this.defaultCharset = defaultCharset; + } + + /** + * Return the default charset, used when the + * {@linkplain #setSupportedMediaTypes(List) content type} does not contain one. + */ + public Charset getDefaultCharset() { + return this.defaultCharset; + } + + @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/freemarker/FreeMarkerView.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/freemarker/FreeMarkerView.java index b15b680185..3c1bcee299 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/freemarker/FreeMarkerView.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/freemarker/FreeMarkerView.java @@ -13,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.reactive.result.view.freemarker; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; +import java.nio.charset.Charset; import java.util.Locale; import java.util.Map; +import java.util.Optional; import freemarker.core.ParseException; import freemarker.template.Configuration; @@ -37,6 +40,7 @@ import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContextException; import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.MediaType; import org.springframework.web.reactive.result.view.AbstractUrlBasedView; import org.springframework.web.server.ServerWebExchange; @@ -158,7 +162,8 @@ public class FreeMarkerView extends AbstractUrlBasedView { } @Override - protected Mono renderInternal(Map renderAttributes, ServerWebExchange exchange) { + protected Mono renderInternal(Map renderAttributes, MediaType contentType, + ServerWebExchange exchange) { // Expose all standard FreeMarker hash models. SimpleHash freeMarkerModel = getTemplateModel(renderAttributes, exchange); if (logger.isDebugEnabled()) { @@ -167,8 +172,8 @@ public class FreeMarkerView extends AbstractUrlBasedView { Locale locale = Locale.getDefault(); // TODO DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer(); try { - // TODO: pass charset - Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream()); + Charset charset = getCharset(contentType).orElse(getDefaultCharset()); + Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream(), charset); getTemplate(locale).process(freeMarkerModel, writer); } catch (IOException ex) { @@ -181,6 +186,10 @@ public class FreeMarkerView extends AbstractUrlBasedView { return exchange.getResponse().writeWith(Flux.just(dataBuffer)); } + private static Optional getCharset(MediaType mediaType) { + return mediaType != null ? Optional.ofNullable(mediaType.getCharset()) : Optional.empty(); + } + /** * Build a FreeMarker template model for the given model Map. *

The default implementation builds a {@link SimpleHash}. diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/UrlBasedViewResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/UrlBasedViewResolverTests.java index 11d61c1dcf..120d685299 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/UrlBasedViewResolverTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/UrlBasedViewResolverTests.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.reactive.result.view; import java.util.Locale; @@ -22,6 +23,7 @@ import org.junit.Test; import reactor.core.publisher.Mono; import org.springframework.context.support.StaticApplicationContext; +import org.springframework.http.MediaType; import org.springframework.web.server.ServerWebExchange; import static org.junit.Assert.assertNotNull; @@ -61,7 +63,8 @@ public class UrlBasedViewResolverTests { } @Override - protected Mono renderInternal(Map attributes, ServerWebExchange exchange) { + protected Mono renderInternal(Map attributes, MediaType contentType, + ServerWebExchange exchange) { return Mono.empty(); } } -- GitLab