Browse Source
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-15036pull/1459/merge
28 changed files with 853 additions and 27 deletions
@ -0,0 +1,127 @@ |
|||||||
|
/* |
||||||
|
* 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.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Locale; |
||||||
|
|
||||||
|
import org.springframework.context.i18n.LocaleContext; |
||||||
|
import org.springframework.context.i18n.SimpleLocaleContext; |
||||||
|
import org.springframework.http.HttpHeaders; |
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest; |
||||||
|
import org.springframework.lang.Nullable; |
||||||
|
import org.springframework.web.server.ServerWebExchange; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@link LocaleContextResolver} implementation that simply uses the primary locale |
||||||
|
* specified in the "Accept-Language" header of the HTTP request (that is, |
||||||
|
* the locale sent by the client browser, normally that of the client's OS). |
||||||
|
* |
||||||
|
* <p>Note: Does not support {@code setLocale}, since the accept header |
||||||
|
* can only be changed through changing the client's locale settings. |
||||||
|
* |
||||||
|
* @author Sebastien Deleuze |
||||||
|
* @since 5.0 |
||||||
|
*/ |
||||||
|
public class AcceptHeaderLocaleContextResolver implements LocaleContextResolver { |
||||||
|
|
||||||
|
private final List<Locale> supportedLocales = new ArrayList<>(4); |
||||||
|
|
||||||
|
private Locale defaultLocale; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Configure supported locales to check against the requested locales |
||||||
|
* determined via {@link HttpHeaders#getAcceptLanguageAsLocales()}. |
||||||
|
* @param locales the supported locales |
||||||
|
*/ |
||||||
|
public void setSupportedLocales(List<Locale> locales) { |
||||||
|
this.supportedLocales.clear(); |
||||||
|
if (locales != null) { |
||||||
|
this.supportedLocales.addAll(locales); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the configured list of supported locales. |
||||||
|
*/ |
||||||
|
public List<Locale> getSupportedLocales() { |
||||||
|
return this.supportedLocales; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Configure a fixed default locale to fall back on if the request does not |
||||||
|
* have an "Accept-Language" header (not set by default). |
||||||
|
* @param defaultLocale the default locale to use |
||||||
|
*/ |
||||||
|
public void setDefaultLocale(Locale defaultLocale) { |
||||||
|
this.defaultLocale = defaultLocale; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The configured default locale, if any. |
||||||
|
*/ |
||||||
|
@Nullable |
||||||
|
public Locale getDefaultLocale() { |
||||||
|
return this.defaultLocale; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public LocaleContext resolveLocaleContext(ServerWebExchange exchange) { |
||||||
|
ServerHttpRequest request = exchange.getRequest(); |
||||||
|
List<Locale> acceptableLocales = request.getHeaders().getAcceptLanguageAsLocales(); |
||||||
|
if (this.defaultLocale != null && acceptableLocales.isEmpty()) { |
||||||
|
return new SimpleLocaleContext(this.defaultLocale); |
||||||
|
} |
||||||
|
Locale requestLocale = acceptableLocales.isEmpty() ? null : acceptableLocales.get(0); |
||||||
|
if (isSupportedLocale(requestLocale)) { |
||||||
|
return new SimpleLocaleContext(requestLocale); |
||||||
|
} |
||||||
|
Locale supportedLocale = findSupportedLocale(request); |
||||||
|
if (supportedLocale != null) { |
||||||
|
return new SimpleLocaleContext(supportedLocale); |
||||||
|
} |
||||||
|
return (defaultLocale != null ? new SimpleLocaleContext(defaultLocale) : new SimpleLocaleContext(requestLocale)); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isSupportedLocale(@Nullable Locale locale) { |
||||||
|
if (locale == null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
List<Locale> supportedLocales = getSupportedLocales(); |
||||||
|
return (supportedLocales.isEmpty() || supportedLocales.contains(locale)); |
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
private Locale findSupportedLocale(ServerHttpRequest request) { |
||||||
|
List<Locale> requestLocales = request.getHeaders().getAcceptLanguageAsLocales(); |
||||||
|
for (Locale locale : requestLocales) { |
||||||
|
if (getSupportedLocales().contains(locale)) { |
||||||
|
return locale; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setLocaleContext(ServerWebExchange exchange, @Nullable LocaleContext locale) { |
||||||
|
throw new UnsupportedOperationException( |
||||||
|
"Cannot change HTTP accept header - use a different locale context resolution strategy"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,92 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 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.Locale; |
||||||
|
import java.util.TimeZone; |
||||||
|
|
||||||
|
import org.springframework.context.i18n.LocaleContext; |
||||||
|
import org.springframework.context.i18n.TimeZoneAwareLocaleContext; |
||||||
|
import org.springframework.lang.Nullable; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.web.server.ServerWebExchange; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@link LocaleContextResolver} implementation |
||||||
|
* that always returns a fixed default locale and optionally time zone. |
||||||
|
* Default is the current JVM's default locale. |
||||||
|
* |
||||||
|
* <p>Note: Does not support {@code setLocale(Context)}, as the fixed |
||||||
|
* locale and time zone cannot be changed. |
||||||
|
* |
||||||
|
* @author Sebastien Deleuze |
||||||
|
* @since 5.0 |
||||||
|
*/ |
||||||
|
public class FixedLocaleContextResolver implements LocaleContextResolver { |
||||||
|
|
||||||
|
private final Locale locale; |
||||||
|
|
||||||
|
private final TimeZone timeZone; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Create a default FixedLocaleResolver, exposing a configured default |
||||||
|
* locale (or the JVM's default locale as fallback). |
||||||
|
*/ |
||||||
|
public FixedLocaleContextResolver() { |
||||||
|
this(Locale.getDefault()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a FixedLocaleResolver that exposes the given locale. |
||||||
|
* @param locale the locale to expose |
||||||
|
*/ |
||||||
|
public FixedLocaleContextResolver(Locale locale) { |
||||||
|
this(locale, null); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a FixedLocaleResolver that exposes the given locale and time zone. |
||||||
|
* @param locale the locale to expose |
||||||
|
* @param timeZone the time zone to expose |
||||||
|
*/ |
||||||
|
public FixedLocaleContextResolver(Locale locale, @Nullable TimeZone timeZone) { |
||||||
|
Assert.notNull(locale, "Locale must not be null"); |
||||||
|
this.locale = locale; |
||||||
|
this.timeZone = timeZone; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public LocaleContext resolveLocaleContext(ServerWebExchange exchange) { |
||||||
|
return new TimeZoneAwareLocaleContext() { |
||||||
|
@Override |
||||||
|
public Locale getLocale() { |
||||||
|
return locale; |
||||||
|
} |
||||||
|
@Override |
||||||
|
public TimeZone getTimeZone() { |
||||||
|
return timeZone; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setLocaleContext(ServerWebExchange exchange, @Nullable LocaleContext localeContext) { |
||||||
|
throw new UnsupportedOperationException("Cannot change fixed locale - use a different locale context resolution strategy"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 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 org.springframework.context.i18n.LocaleContext; |
||||||
|
import org.springframework.lang.Nullable; |
||||||
|
import org.springframework.web.server.ServerWebExchange; |
||||||
|
|
||||||
|
/** |
||||||
|
* Interface for web-based locale context resolution strategies that allows |
||||||
|
* for both locale context resolution via the request and locale context modification |
||||||
|
* via the HTTP exchange. |
||||||
|
* |
||||||
|
* <p>The {@link org.springframework.context.i18n.LocaleContext} object can potentially |
||||||
|
* includes associated time zone and other locale related information. |
||||||
|
* |
||||||
|
* @author Sebastien Deleuze |
||||||
|
* @since 5.0 |
||||||
|
* @see LocaleContext |
||||||
|
*/ |
||||||
|
public interface LocaleContextResolver { |
||||||
|
|
||||||
|
/** |
||||||
|
* Resolve the current locale context via the given exchange. |
||||||
|
* |
||||||
|
* <p>The returned context may be a |
||||||
|
* {@link org.springframework.context.i18n.TimeZoneAwareLocaleContext}, |
||||||
|
* containing a locale with associated time zone information. |
||||||
|
* Simply apply an {@code instanceof} check and downcast accordingly. |
||||||
|
* <p>Custom resolver implementations may also return extra settings in |
||||||
|
* the returned context, which again can be accessed through downcasting. |
||||||
|
* @param exchange current server exchange |
||||||
|
* @return the current locale context (never {@code null} |
||||||
|
*/ |
||||||
|
LocaleContext resolveLocaleContext(ServerWebExchange exchange); |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the current locale context to the given one, |
||||||
|
* potentially including a locale with associated time zone information. |
||||||
|
* @param exchange current server exchange |
||||||
|
* @param localeContext the new locale context, or {@code null} to clear the locale |
||||||
|
* @throws UnsupportedOperationException if the LocaleResolver implementation |
||||||
|
* does not support dynamic changing of the locale or time zone |
||||||
|
* @see org.springframework.context.i18n.SimpleLocaleContext |
||||||
|
* @see org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext |
||||||
|
*/ |
||||||
|
void setLocaleContext(ServerWebExchange exchange, @Nullable LocaleContext localeContext); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
/** |
||||||
|
* Locale related support classes. |
||||||
|
* Provides standard LocaleContextResolver implementations. |
||||||
|
*/ |
||||||
|
@NonNullApi |
||||||
|
package org.springframework.web.server.i18n; |
||||||
|
|
||||||
|
import org.springframework.lang.NonNullApi; |
@ -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()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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"; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue