Introduce LocaleContextResolver in WebFlux
This commit introduces LocaleContextResolver interface, which is used at ServerWebExchange level to resolve Locale, TimeZone and other i18n related informations. It follows Spring MVC locale resolution patterns with a few differences: - Only LocaleContextResolver is supported since LocaleResolver is less flexible - Support is implemented in the org.springframework.web.server.i18n package of spring-web module rather than in spring-webflux in order to be able to leverage it at ServerWebExchange level 2 implementations are provided: - FixedLocaleContextResolver - AcceptHeaderLocaleContextResolver It can be configured with both functional or annotation-based APIs. Issue: SPR-15036
This commit is contained in:
@@ -58,9 +58,11 @@ import org.springframework.web.reactive.result.method.annotation.ResponseBodyRes
|
||||
import org.springframework.web.reactive.result.method.annotation.ResponseEntityResultHandler;
|
||||
import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebExceptionHandler;
|
||||
import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
|
||||
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
|
||||
|
||||
/**
|
||||
* The main class for Spring WebFlux configuration.
|
||||
@@ -266,6 +268,18 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
|
||||
return serverCodecConfigurer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to plug a sub-class of {@link LocaleContextResolver}.
|
||||
*/
|
||||
protected LocaleContextResolver createLocaleContextResolver() {
|
||||
return new AcceptHeaderLocaleContextResolver();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LocaleContextResolver localeContextResolver() {
|
||||
return createLocaleContextResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to configure the HTTP message readers and writers to use.
|
||||
*/
|
||||
|
||||
@@ -27,9 +27,11 @@ import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||
import org.springframework.web.server.WebExceptionHandler;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
|
||||
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link HandlerStrategies.Builder}.
|
||||
@@ -47,6 +49,8 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
|
||||
|
||||
private final List<WebExceptionHandler> exceptionHandlers = new ArrayList<>();
|
||||
|
||||
private LocaleContextResolver localeContextResolver;
|
||||
|
||||
|
||||
public DefaultHandlerStrategiesBuilder() {
|
||||
this.codecConfigurer.registerDefaults(false);
|
||||
@@ -55,6 +59,7 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
|
||||
public void defaultConfiguration() {
|
||||
this.codecConfigurer.registerDefaults(true);
|
||||
exceptionHandler(new ResponseStatusExceptionHandler());
|
||||
localeContextResolver(new AcceptHeaderLocaleContextResolver());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,11 +99,18 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerStrategies.Builder localeContextResolver(LocaleContextResolver localeContextResolver) {
|
||||
Assert.notNull(localeContextResolver, "'localeContextResolver' must not be null");
|
||||
this.localeContextResolver = localeContextResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerStrategies build() {
|
||||
return new DefaultHandlerStrategies(this.codecConfigurer.getReaders(),
|
||||
this.codecConfigurer.getWriters(), this.viewResolvers, this.webFilters,
|
||||
this.exceptionHandlers);
|
||||
this.exceptionHandlers, this.localeContextResolver);
|
||||
}
|
||||
|
||||
|
||||
@@ -114,18 +126,23 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
|
||||
|
||||
private final List<WebExceptionHandler> exceptionHandlers;
|
||||
|
||||
private final LocaleContextResolver localeContextResolver;
|
||||
|
||||
|
||||
public DefaultHandlerStrategies(
|
||||
List<HttpMessageReader<?>> messageReaders,
|
||||
List<HttpMessageWriter<?>> messageWriters,
|
||||
List<ViewResolver> viewResolvers,
|
||||
List<WebFilter> webFilters,
|
||||
List<WebExceptionHandler> exceptionHandlers) {
|
||||
List<WebExceptionHandler> exceptionHandlers,
|
||||
LocaleContextResolver localeContextResolver) {
|
||||
|
||||
this.messageReaders = unmodifiableCopy(messageReaders);
|
||||
this.messageWriters = unmodifiableCopy(messageWriters);
|
||||
this.viewResolvers = unmodifiableCopy(viewResolvers);
|
||||
this.webFilters = unmodifiableCopy(webFilters);
|
||||
this.exceptionHandlers = unmodifiableCopy(exceptionHandlers);
|
||||
this.localeContextResolver = localeContextResolver;
|
||||
}
|
||||
|
||||
private static <T> List<T> unmodifiableCopy(List<? extends T> list) {
|
||||
@@ -156,6 +173,11 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
|
||||
public List<WebExceptionHandler> exceptionHandlers() {
|
||||
return this.exceptionHandlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleContextResolver localeContextResolver() {
|
||||
return this.localeContextResolver;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
@@ -156,7 +155,7 @@ class DefaultRenderingResponseBuilder implements RenderingResponse.Builder {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
writeStatusAndHeaders(response);
|
||||
MediaType contentType = exchange.getResponse().getHeaders().getContentType();
|
||||
Locale locale = resolveLocale(exchange);
|
||||
Locale locale = exchange.getLocaleContext().getLocale();
|
||||
Stream<ViewResolver> viewResolverStream = context.viewResolvers().stream();
|
||||
|
||||
return Flux.fromStream(viewResolverStream)
|
||||
@@ -167,11 +166,6 @@ class DefaultRenderingResponseBuilder implements RenderingResponse.Builder {
|
||||
.flatMap(view -> view.render(model(), contentType, exchange));
|
||||
}
|
||||
|
||||
private Locale resolveLocale(ServerWebExchange exchange) {
|
||||
List<Locale> locales = exchange.getRequest().getHeaders().getAcceptLanguageAsLocales();
|
||||
return locales.isEmpty() ? Locale.getDefault() : locales.get(0);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||
import org.springframework.web.server.WebExceptionHandler;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
|
||||
@@ -35,6 +36,7 @@ import org.springframework.web.server.WebFilter;
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
* @see RouterFunctions#toHttpHandler(RouterFunction, HandlerStrategies)
|
||||
*/
|
||||
@@ -72,6 +74,12 @@ public interface HandlerStrategies {
|
||||
*/
|
||||
List<WebExceptionHandler> exceptionHandlers();
|
||||
|
||||
/**
|
||||
* Return the {@link LocaleContextResolver} to be used for resolving locale context.
|
||||
* @return the locale context resolver
|
||||
*/
|
||||
LocaleContextResolver localeContextResolver();
|
||||
|
||||
|
||||
// Static methods
|
||||
|
||||
@@ -146,6 +154,13 @@ public interface HandlerStrategies {
|
||||
*/
|
||||
Builder exceptionHandler(WebExceptionHandler exceptionHandler);
|
||||
|
||||
/**
|
||||
* Add the given locale context resolver to this builder.
|
||||
* @param localeContextResolver the locale context resolver to add
|
||||
* @return this builder
|
||||
*/
|
||||
Builder localeContextResolver(LocaleContextResolver localeContextResolver);
|
||||
|
||||
/**
|
||||
* Builds the {@link HandlerStrategies}.
|
||||
* @return the built strategies
|
||||
|
||||
@@ -199,6 +199,7 @@ public abstract class RouterFunctions {
|
||||
return WebHttpHandlerBuilder.webHandler(webHandler)
|
||||
.filters(strategies.webFilters())
|
||||
.exceptionHandlers(strategies.exceptionHandlers())
|
||||
.localeContextResolver(strategies.localeContextResolver())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ import java.util.TimeZone;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceResolvable;
|
||||
import org.springframework.context.NoSuchMessageException;
|
||||
import org.springframework.context.i18n.LocaleContext;
|
||||
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -84,9 +86,10 @@ public class RequestContext {
|
||||
this.model = model;
|
||||
this.messageSource = messageSource;
|
||||
|
||||
List<Locale> locales = exchange.getRequest().getHeaders().getAcceptLanguageAsLocales();
|
||||
this.locale = locales.isEmpty() ? Locale.getDefault() : locales.get(0);
|
||||
this.timeZone = TimeZone.getDefault(); // TODO
|
||||
LocaleContext localeContext = exchange.getLocaleContext();
|
||||
this.locale = localeContext.getLocale();
|
||||
this.timeZone = (localeContext instanceof TimeZoneAwareLocaleContext ?
|
||||
((TimeZoneAwareLocaleContext)localeContext).getTimeZone() : TimeZone.getDefault());
|
||||
|
||||
this.defaultHtmlEscape = null; // TODO
|
||||
this.dataValueProcessor = dataValueProcessor;
|
||||
|
||||
@@ -201,8 +201,7 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport
|
||||
Model model = result.getModel();
|
||||
MethodParameter parameter = result.getReturnTypeSource();
|
||||
|
||||
List<Locale> locales = exchange.getRequest().getHeaders().getAcceptLanguageAsLocales();
|
||||
Locale locale = locales.isEmpty() ? Locale.getDefault() : locales.get(0);
|
||||
Locale locale = exchange.getLocaleContext().getLocale();
|
||||
|
||||
Class<?> clazz = valueType.getRawClass();
|
||||
if (clazz == null) {
|
||||
|
||||
@@ -21,7 +21,6 @@ import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -189,8 +188,7 @@ public class FreeMarkerView extends AbstractUrlBasedView {
|
||||
logger.debug("Rendering FreeMarker template [" + getUrl() + "].");
|
||||
}
|
||||
|
||||
List<Locale> locales = exchange.getRequest().getHeaders().getAcceptLanguageAsLocales();
|
||||
Locale locale = locales.isEmpty() ? Locale.getDefault() : locales.get(0);
|
||||
Locale locale = exchange.getLocaleContext().getLocale();
|
||||
|
||||
DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer();
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.function.server;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.result.view.View;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.i18n.FixedLocaleContextResolver;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class LocaleContextResolverIntegrationTests extends AbstractRouterFunctionIntegrationTests {
|
||||
|
||||
private final WebClient webClient = WebClient.create();
|
||||
|
||||
@Test
|
||||
public void fixedLocale() {
|
||||
Mono<ClientResponse> result = webClient
|
||||
.get()
|
||||
.uri("http://localhost:" + this.port + "/")
|
||||
.exchange();
|
||||
|
||||
StepVerifier
|
||||
.create(result)
|
||||
.consumeNextWith(response -> {
|
||||
assertEquals(HttpStatus.OK, response.statusCode());
|
||||
assertEquals(Locale.GERMANY, response.headers().asHttpHeaders().getContentLanguage());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RouterFunction<?> routerFunction() {
|
||||
return RouterFunctions.route(RequestPredicates.path("/"), this::render);
|
||||
}
|
||||
|
||||
public Mono<RenderingResponse> render(ServerRequest request) {
|
||||
return RenderingResponse.create("foo").build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HandlerStrategies handlerStrategies() {
|
||||
return HandlerStrategies.builder()
|
||||
.viewResolver(new DummyViewResolver())
|
||||
.localeContextResolver(new FixedLocaleContextResolver(Locale.GERMANY))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static class DummyViewResolver implements ViewResolver {
|
||||
|
||||
@Override
|
||||
public Mono<View> resolveViewName(String viewName, Locale locale) {
|
||||
return Mono.just(new DummyView(locale));
|
||||
}
|
||||
}
|
||||
|
||||
private static class DummyView implements View {
|
||||
|
||||
private final Locale locale;
|
||||
|
||||
public DummyView(Locale locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MediaType> getSupportedMediaTypes() {
|
||||
return Collections.singletonList(MediaType.TEXT_HTML);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> render(@Nullable Map<String, ?> model, @Nullable MediaType contentType,
|
||||
ServerWebExchange exchange) {
|
||||
exchange.getResponse().getHeaders().setContentLanguage(locale);
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
|
||||
import org.springframework.web.server.session.MockWebSessionManager;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
@@ -82,7 +83,8 @@ public class SessionAttributeMethodArgumentResolverTests {
|
||||
WebSessionManager sessionManager = new MockWebSessionManager(this.session);
|
||||
|
||||
ServerHttpRequest request = MockServerHttpRequest.get("/").build();
|
||||
this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager, ServerCodecConfigurer.create());
|
||||
this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(),
|
||||
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
|
||||
this.handleMethod = ReflectionUtils.findMethod(getClass(), "handleWithSessionAttribute", (Class<?>[]) null);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.web.reactive.BindingContext;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
|
||||
import org.springframework.web.server.session.DefaultWebSession;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
@@ -65,7 +66,7 @@ public class WebSessionArgumentResolverTests {
|
||||
WebSessionManager manager = exchange -> Mono.just(session);
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/").build();
|
||||
ServerWebExchange exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(),
|
||||
manager, ServerCodecConfigurer.create());
|
||||
manager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
|
||||
MethodParameter param = this.testMethod.arg(WebSession.class);
|
||||
Object actual = this.resolver.resolveArgument(param, context, exchange).block();
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.result.view;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.reactive.config.ViewResolverRegistry;
|
||||
import org.springframework.web.reactive.config.WebFluxConfigurationSupport;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.result.method.annotation.AbstractRequestMappingIntegrationTests;
|
||||
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.i18n.FixedLocaleContextResolver;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class LocaleContextResolverIntegrationTests extends AbstractRequestMappingIntegrationTests {
|
||||
|
||||
private final WebClient webClient = WebClient.create();
|
||||
|
||||
@Test
|
||||
public void fixedLocale() {
|
||||
Mono<ClientResponse> result = webClient
|
||||
.get()
|
||||
.uri("http://localhost:" + this.port + "/")
|
||||
.exchange();
|
||||
|
||||
StepVerifier
|
||||
.create(result)
|
||||
.consumeNextWith(response -> {
|
||||
assertEquals(HttpStatus.OK, response.statusCode());
|
||||
assertEquals(Locale.GERMANY, response.headers().asHttpHeaders().getContentLanguage());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ApplicationContext initApplicationContext() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.register(WebConfig.class);
|
||||
context.refresh();
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(resourcePattern = "**/LocaleContextResolverIntegrationTests*.class")
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
static class WebConfig extends WebFluxConfigurationSupport {
|
||||
|
||||
@Override
|
||||
protected LocaleContextResolver createLocaleContextResolver() {
|
||||
return new FixedLocaleContextResolver(Locale.GERMANY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureViewResolvers(ViewResolverRegistry registry) {
|
||||
registry.viewResolver((viewName, locale) -> Mono.just(new DummyView(locale)));
|
||||
}
|
||||
|
||||
private static class DummyView implements View {
|
||||
|
||||
private final Locale locale;
|
||||
|
||||
public DummyView(Locale locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MediaType> getSupportedMediaTypes() {
|
||||
return Collections.singletonList(MediaType.TEXT_HTML);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> render(@Nullable Map<String, ?> model, @Nullable MediaType contentType,
|
||||
ServerWebExchange exchange) {
|
||||
exchange.getResponse().getHeaders().setContentLanguage(locale);
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Controller
|
||||
@SuppressWarnings("unused")
|
||||
static class TestController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String foo() {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user