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:
@@ -23,6 +23,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
@@ -260,6 +261,13 @@ public class MockServerHttpRequest extends AbstractServerHttpRequest {
|
||||
*/
|
||||
B acceptCharset(Charset... acceptableCharsets);
|
||||
|
||||
/**
|
||||
* Set the list of acceptable {@linkplain Locale locales}, as specified
|
||||
* by the {@code Accept-Languages} header.
|
||||
* @param acceptableLocales the acceptable locales
|
||||
*/
|
||||
B acceptLanguageAsLocales(Locale... acceptableLocales);
|
||||
|
||||
/**
|
||||
* Set the value of the {@code If-Modified-Since} header.
|
||||
* <p>The date should be specified as the number of milliseconds since
|
||||
@@ -420,6 +428,12 @@ public class MockServerHttpRequest extends AbstractServerHttpRequest {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BodyBuilder acceptLanguageAsLocales(Locale... acceptableLocales) {
|
||||
this.headers.setAcceptLanguageAsLocales(Arrays.asList(acceptableLocales));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BodyBuilder contentLength(long contentLength) {
|
||||
this.headers.setContentLength(contentLength);
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.mock.http.server.reactive.test;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.web.server.ServerWebExchangeDecorator;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
|
||||
import org.springframework.web.server.session.DefaultWebSessionManager;
|
||||
|
||||
/**
|
||||
@@ -36,7 +37,8 @@ public class MockServerWebExchange extends ServerWebExchangeDecorator {
|
||||
|
||||
public MockServerWebExchange(MockServerHttpRequest request) {
|
||||
super(new DefaultServerWebExchange(
|
||||
request, new MockServerHttpResponse(), new DefaultWebSessionManager(), ServerCodecConfigurer.create()));
|
||||
request, new MockServerHttpResponse(), new DefaultWebSessionManager(),
|
||||
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.server.i18n;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static java.util.Locale.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AcceptHeaderLocaleContextResolver}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class AcceptHeaderLocaleContextResolverTests {
|
||||
|
||||
private AcceptHeaderLocaleContextResolver resolver = new AcceptHeaderLocaleContextResolver();
|
||||
|
||||
|
||||
@Test
|
||||
public void resolve() throws Exception {
|
||||
assertEquals(CANADA, this.resolver.resolveLocaleContext(exchange(CANADA)).getLocale());
|
||||
assertEquals(US, this.resolver.resolveLocaleContext(exchange(US, CANADA)).getLocale());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvePreferredSupported() throws Exception {
|
||||
this.resolver.setSupportedLocales(Collections.singletonList(CANADA));
|
||||
assertEquals(CANADA, this.resolver.resolveLocaleContext(exchange(US, CANADA)).getLocale());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvePreferredNotSupported() throws Exception {
|
||||
this.resolver.setSupportedLocales(Collections.singletonList(CANADA));
|
||||
assertEquals(US, this.resolver.resolveLocaleContext(exchange(US, UK)).getLocale());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvePreferredNotSupportedWithDefault() {
|
||||
this.resolver.setSupportedLocales(Arrays.asList(US, JAPAN));
|
||||
this.resolver.setDefaultLocale(JAPAN);
|
||||
|
||||
MockServerWebExchange exchange = new MockServerWebExchange(MockServerHttpRequest
|
||||
.get("/")
|
||||
.acceptLanguageAsLocales(KOREA)
|
||||
.build());
|
||||
assertEquals(JAPAN, this.resolver.resolveLocaleContext(exchange).getLocale());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultLocale() throws Exception {
|
||||
this.resolver.setDefaultLocale(JAPANESE);
|
||||
MockServerWebExchange exchange = new MockServerWebExchange(MockServerHttpRequest
|
||||
.get("/")
|
||||
.build());
|
||||
assertEquals(JAPANESE, this.resolver.resolveLocaleContext(exchange).getLocale());
|
||||
|
||||
exchange = new MockServerWebExchange(MockServerHttpRequest
|
||||
.get("/")
|
||||
.acceptLanguageAsLocales(US)
|
||||
.build());
|
||||
assertEquals(US, this.resolver.resolveLocaleContext(exchange).getLocale());
|
||||
}
|
||||
|
||||
|
||||
private ServerWebExchange exchange(Locale... locales) {
|
||||
return new MockServerWebExchange(MockServerHttpRequest
|
||||
.get("")
|
||||
.acceptLanguageAsLocales(locales)
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.springframework.web.server.i18n;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static java.util.Locale.CANADA;
|
||||
import static java.util.Locale.FRANCE;
|
||||
import static java.util.Locale.US;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link FixedLocaleContextResolver}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class FixedLocaleContextResolverTests {
|
||||
|
||||
private FixedLocaleContextResolver resolver;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
Locale.setDefault(US);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveDefaultLocale() {
|
||||
this.resolver = new FixedLocaleContextResolver();
|
||||
assertEquals(US, this.resolver.resolveLocaleContext(exchange()).getLocale());
|
||||
assertEquals(US, this.resolver.resolveLocaleContext(exchange(CANADA)).getLocale());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveCustomizedLocale() {
|
||||
this.resolver = new FixedLocaleContextResolver(FRANCE);
|
||||
assertEquals(FRANCE, this.resolver.resolveLocaleContext(exchange()).getLocale());
|
||||
assertEquals(FRANCE, this.resolver.resolveLocaleContext(exchange(CANADA)).getLocale());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveCustomizedAndTimeZoneLocale() {
|
||||
TimeZone timeZone = TimeZone.getTimeZone(ZoneId.of("UTC"));
|
||||
this.resolver = new FixedLocaleContextResolver(FRANCE, timeZone);
|
||||
TimeZoneAwareLocaleContext context = (TimeZoneAwareLocaleContext)this.resolver.resolveLocaleContext(exchange());
|
||||
assertEquals(FRANCE, context.getLocale());
|
||||
assertEquals(timeZone, context.getTimeZone());
|
||||
}
|
||||
|
||||
private ServerWebExchange exchange(Locale... locales) {
|
||||
return new MockServerWebExchange(MockServerHttpRequest
|
||||
.get("")
|
||||
.acceptLanguageAsLocales(locales)
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse
|
||||
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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -60,7 +61,8 @@ public class DefaultWebSessionManagerTests {
|
||||
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/path").build();
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
this.exchange = new DefaultServerWebExchange(request, response, this.manager, ServerCodecConfigurer.create());
|
||||
this.exchange = new DefaultServerWebExchange(request, response, this.manager,
|
||||
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user