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.
This commit is contained in:
parent
a746c3c54e
commit
5f941c1dd1
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.web.reactive.result.view;
|
package org.springframework.web.reactive.result.view;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -45,6 +47,8 @@ public abstract class AbstractView implements View, ApplicationContextAware {
|
||||||
|
|
||||||
private final List<MediaType> mediaTypes = new ArrayList<>(4);
|
private final List<MediaType> mediaTypes = new ArrayList<>(4);
|
||||||
|
|
||||||
|
private Charset defaultCharset = StandardCharsets.UTF_8;
|
||||||
|
|
||||||
private ApplicationContext applicationContext;
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,6 +77,24 @@ public abstract class AbstractView implements View, ApplicationContextAware {
|
||||||
return this.mediaTypes;
|
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
|
@Override
|
||||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||||
this.applicationContext = applicationContext;
|
this.applicationContext = applicationContext;
|
||||||
|
@ -104,7 +126,7 @@ public abstract class AbstractView implements View, ApplicationContextAware {
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> mergedModel = getModelAttributes(model, exchange);
|
Map<String, Object> 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.
|
* Subclasses must implement this method to actually render the view.
|
||||||
* @param renderAttributes combined output Map (never {@code null}),
|
* @param renderAttributes combined output Map (never {@code null}),
|
||||||
* with dynamic values taking precedence over static attributes
|
* with dynamic values taking precedence over static attributes
|
||||||
* @param exchange current exchange
|
* @param contentType the content type selected to render with which should
|
||||||
* @return {@code Mono} to represent when and if rendering succeeds
|
* 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<Void> renderInternal(Map<String, Object> renderAttributes,
|
protected abstract Mono<Void> renderInternal(Map<String, Object> renderAttributes,
|
||||||
ServerWebExchange exchange);
|
MediaType contentType, ServerWebExchange exchange);
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.web.reactive.result.view;
|
package org.springframework.web.reactive.result.view;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -194,6 +195,7 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes
|
||||||
protected AbstractUrlBasedView createUrlBasedView(String viewName) {
|
protected AbstractUrlBasedView createUrlBasedView(String viewName) {
|
||||||
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
|
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
|
||||||
view.setSupportedMediaTypes(getSupportedMediaTypes());
|
view.setSupportedMediaTypes(getSupportedMediaTypes());
|
||||||
|
view.setDefaultCharset(getDefaultCharset());
|
||||||
view.setUrl(getPrefix() + viewName + getSuffix());
|
view.setUrl(getPrefix() + viewName + getSuffix());
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.web.reactive.result.view;
|
package org.springframework.web.reactive.result.view;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -38,6 +40,8 @@ public abstract class ViewResolverSupport implements ApplicationContextAware, Or
|
||||||
|
|
||||||
private List<MediaType> mediaTypes = new ArrayList<>(4);
|
private List<MediaType> mediaTypes = new ArrayList<>(4);
|
||||||
|
|
||||||
|
private Charset defaultCharset = StandardCharsets.UTF_8;
|
||||||
|
|
||||||
private ApplicationContext applicationContext;
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
private int order = Integer.MAX_VALUE;
|
private int order = Integer.MAX_VALUE;
|
||||||
|
@ -67,6 +71,25 @@ public abstract class ViewResolverSupport implements ApplicationContextAware, Or
|
||||||
return this.mediaTypes;
|
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
|
@Override
|
||||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||||
this.applicationContext = applicationContext;
|
this.applicationContext = applicationContext;
|
||||||
|
|
|
@ -13,14 +13,17 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.web.reactive.result.view.freemarker;
|
package org.springframework.web.reactive.result.view.freemarker;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import freemarker.core.ParseException;
|
import freemarker.core.ParseException;
|
||||||
import freemarker.template.Configuration;
|
import freemarker.template.Configuration;
|
||||||
|
@ -37,6 +40,7 @@ import org.springframework.beans.factory.BeanFactoryUtils;
|
||||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
import org.springframework.context.ApplicationContextException;
|
import org.springframework.context.ApplicationContextException;
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
import org.springframework.core.io.buffer.DataBuffer;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.reactive.result.view.AbstractUrlBasedView;
|
import org.springframework.web.reactive.result.view.AbstractUrlBasedView;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
@ -158,7 +162,8 @@ public class FreeMarkerView extends AbstractUrlBasedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Mono<Void> renderInternal(Map<String, Object> renderAttributes, ServerWebExchange exchange) {
|
protected Mono<Void> renderInternal(Map<String, Object> renderAttributes, MediaType contentType,
|
||||||
|
ServerWebExchange exchange) {
|
||||||
// Expose all standard FreeMarker hash models.
|
// Expose all standard FreeMarker hash models.
|
||||||
SimpleHash freeMarkerModel = getTemplateModel(renderAttributes, exchange);
|
SimpleHash freeMarkerModel = getTemplateModel(renderAttributes, exchange);
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
|
@ -167,8 +172,8 @@ public class FreeMarkerView extends AbstractUrlBasedView {
|
||||||
Locale locale = Locale.getDefault(); // TODO
|
Locale locale = Locale.getDefault(); // TODO
|
||||||
DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer();
|
DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer();
|
||||||
try {
|
try {
|
||||||
// TODO: pass charset
|
Charset charset = getCharset(contentType).orElse(getDefaultCharset());
|
||||||
Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream());
|
Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream(), charset);
|
||||||
getTemplate(locale).process(freeMarkerModel, writer);
|
getTemplate(locale).process(freeMarkerModel, writer);
|
||||||
}
|
}
|
||||||
catch (IOException ex) {
|
catch (IOException ex) {
|
||||||
|
@ -181,6 +186,10 @@ public class FreeMarkerView extends AbstractUrlBasedView {
|
||||||
return exchange.getResponse().writeWith(Flux.just(dataBuffer));
|
return exchange.getResponse().writeWith(Flux.just(dataBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Optional<Charset> getCharset(MediaType mediaType) {
|
||||||
|
return mediaType != null ? Optional.ofNullable(mediaType.getCharset()) : Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a FreeMarker template model for the given model Map.
|
* Build a FreeMarker template model for the given model Map.
|
||||||
* <p>The default implementation builds a {@link SimpleHash}.
|
* <p>The default implementation builds a {@link SimpleHash}.
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.web.reactive.result.view;
|
package org.springframework.web.reactive.result.view;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -22,6 +23,7 @@ import org.junit.Test;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.context.support.StaticApplicationContext;
|
import org.springframework.context.support.StaticApplicationContext;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
@ -61,7 +63,8 @@ public class UrlBasedViewResolverTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Mono<Void> renderInternal(Map<String, Object> attributes, ServerWebExchange exchange) {
|
protected Mono<Void> renderInternal(Map<String, Object> attributes, MediaType contentType,
|
||||||
|
ServerWebExchange exchange) {
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue