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:
@@ -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