From d8ad11909583fbeff42f6ff4c6cc21afa4202adf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Laugks?= Date: Mon, 1 Jul 2024 19:52:32 +0200 Subject: [PATCH] Release 0.3.0 - Fix: Request with QueryString - Handling trailing slash in RequestURI --- README.md | 24 ++--- pom.xml | 2 +- .../RequestURILocaleInterceptor.java | 33 ++++--- .../RequestURILocaleInterceptorTest.java | 98 ++++++++++--------- 4 files changed, 86 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 057b4f2..ef55817 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,11 @@ Handling Locale as first part of [RequestURI](https://jakarta.ee/specifications/ Example: ``` -HTTP Request RequestURI +HTTP Request RequestURI -https://foo.bar/{locale}/some/path.html -> /{locale}/some/path.html -https://foo.bar/{locale}/a.html -> /{locale}/a.html -https://foo.bar/{locale}/xyz?a=b -> /{locale}/xyz +https://foo.bar/{locale}/some/path.html -> /{locale}/some/path.html +https://foo.bar/{locale}/a.html -> /{locale}/a.html +https://foo.bar/{locale}/xyz?a=b -> /{locale}/xyz ``` An example in action can be seen [here](https://spring-boot-xliff-example.alaugks.dev/). @@ -19,6 +19,7 @@ An example in action can be seen [here](https://spring-boot-xliff-example.alaugk | Version | Description | |:--------|:--------------------------------------------------------------------------------------------------------------------------| +| 0.3.0 | [Release notes](https://github.com/alaugks/spring-requesturi-locale-interceptor/releases/tag/0.3.0) | | 0.2.0 | [Release notes](https://github.com/alaugks/spring-requesturi-locale-interceptor/releases/tag/0.2.0) / **Breaking Change** | | 0.1.0 | [Release notes](https://github.com/alaugks/spring-requesturi-locale-interceptor/releases/tag/0.1.0) | @@ -32,14 +33,14 @@ An example in action can be seen [here](https://spring-boot-xliff-example.alaugk io.github.alaugks spring-requesturi-locale-interceptor - 0.2.0 + 0.3.0 ``` ### Gradle ``` -implementation group: 'io.github.alaugks', name: 'spring-requesturi-locale-interceptor', version: '0.2.0' +implementation group: 'io.github.alaugks', name: 'spring-requesturi-locale-interceptor', version: '0.3.0' ``` @@ -129,17 +130,12 @@ public class WebMvcConfigurerConfig implements WebMvcConfigurer { RequestURILocaleInterceptor interceptor = RequestURILocaleInterceptor .builder(this.defaultLocale) .supportedLocales(this.supportedLocales) - .defaultRequestURI( - String.format( - "/%s/home", - this.defaultLocale.toString() - ) - ) + .defaultRequestURI("/en/home") .build(); - - // Exclude from Interceptor + registry.addInterceptor(urlInterceptor) .addPathPatterns("/**") + // Exclude from Interceptor .excludePathPatterns("/static/**", "/error"); } } diff --git a/pom.xml b/pom.xml index 89020b5..e280493 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.alaugks spring-requesturi-locale-interceptor - 0.2.0 + 0.3.0 jar ${project.groupId}:${project.artifactId} diff --git a/src/main/java/io/github/alaugks/spring/requesturilocaleinterceptor/RequestURILocaleInterceptor.java b/src/main/java/io/github/alaugks/spring/requesturilocaleinterceptor/RequestURILocaleInterceptor.java index fec3314..86b8a24 100644 --- a/src/main/java/io/github/alaugks/spring/requesturilocaleinterceptor/RequestURILocaleInterceptor.java +++ b/src/main/java/io/github/alaugks/spring/requesturilocaleinterceptor/RequestURILocaleInterceptor.java @@ -19,7 +19,7 @@ public class RequestURILocaleInterceptor implements HandlerInterceptor { private static final String PATH_DELIMITER = "/"; private final Locale defaultLocale; private final List supportedLocales; - private final String defaultHomePath; + private String defaultHomePath; public RequestURILocaleInterceptor(Builder builder) { this.defaultLocale = builder.defaultLocale; @@ -59,10 +59,6 @@ public RequestURILocaleInterceptor build() { this.supportedLocales = new ArrayList<>(); } - if (this.defaultRequestURI == null) { - this.defaultRequestURI = PATH_DELIMITER + this.defaultLocale; - } - return new RequestURILocaleInterceptor(this); } } @@ -70,14 +66,27 @@ public RequestURILocaleInterceptor build() { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { try { - String[] explodedRequestUri = request.getRequestURI().substring(1).split(PATH_DELIMITER); - LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request); - if (localeResolver == null) { throw new IllegalStateException("LocaleResolver not found"); } + if (this.defaultHomePath == null) { + this.defaultHomePath = PATH_DELIMITER + this.formatLocale(this.defaultLocale); + } + + // Remove Leading Slash + String requestUri = request.getRequestURI().substring(1); + + // Remove Trailing Slash + String trailingSlash = ""; + if (requestUri.endsWith(PATH_DELIMITER)) { + requestUri = requestUri.substring(0, requestUri.length() - 1); + trailingSlash = PATH_DELIMITER; + } + + String[] explodedRequestUri = requestUri.split(PATH_DELIMITER); + Locale localeUri = Locale.forLanguageTag(explodedRequestUri[0]); boolean isLocaleSupported = this.supportedLocales @@ -89,12 +98,12 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons return true; } - URL url = this.createUri(request, this.joinUri(explodedRequestUri)).toURL(); + URL url = this.createUri(request, this.joinUri(explodedRequestUri, trailingSlash)).toURL(); // Send redirect only with path + query. // No domain handling domain/ip vs. proxies and forwarded. response.sendRedirect( - url.getPath() + (url.getQuery() != null ? url.getQuery() : "") + url.getPath() + (url.getQuery() != null ? "?" + url.getQuery() : "") ); return false; @@ -104,7 +113,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons } } - private String joinUri(String... uri) { + private String joinUri(String[] uri, String trailingSlash) { String joinedUri = String.join( PATH_DELIMITER, Arrays.copyOfRange(uri, 1, uri.length) @@ -112,7 +121,7 @@ private String joinUri(String... uri) { String path = !joinedUri.isEmpty() ? PATH_DELIMITER + joinedUri : ""; return !path.isEmpty() - ? PATH_DELIMITER + this.formatLocale(this.defaultLocale) + path + ? PATH_DELIMITER + this.formatLocale(this.defaultLocale) + path + trailingSlash : String.format(this.defaultHomePath, this.formatLocale(this.defaultLocale)); } diff --git a/src/test/java/io/github/alaugks/spring/requesturilocaleinterceptor/RequestURILocaleInterceptorTest.java b/src/test/java/io/github/alaugks/spring/requesturilocaleinterceptor/RequestURILocaleInterceptorTest.java index c14a5bb..b99c109 100644 --- a/src/test/java/io/github/alaugks/spring/requesturilocaleinterceptor/RequestURILocaleInterceptorTest.java +++ b/src/test/java/io/github/alaugks/spring/requesturilocaleinterceptor/RequestURILocaleInterceptorTest.java @@ -5,11 +5,16 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import io.github.alaugks.spring.requesturilocaleinterceptor.mocks.MockLocaleResolver; +import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.web.servlet.DispatcherServlet; @@ -35,35 +40,63 @@ void beforeEach() { this.mockedResponse = new MockHttpServletResponse(); } - void initUrlLocaleInterceptor(Locale defaultLocale) { + void initUrlLocaleInterceptor(Locale defaultLocale, String defaultRequestUri) { RequestURILocaleInterceptor interceptor = RequestURILocaleInterceptor .builder(defaultLocale) .supportedLocales(supportedLocales) - .defaultRequestURI("/en/home") + .defaultRequestURI(defaultRequestUri) .build(); interceptor.preHandle(this.mockRequest, this.mockedResponse, null); } - @Test - void test_uriDePath() { - this.mockRequest.setRequestURI("/de/home"); - this.initUrlLocaleInterceptor(Locale.forLanguageTag("en")); - assertEquals("de", this.mockLocaleResolver.resolveLocale(this.mockRequest).getLanguage()); + @ParameterizedTest + @MethodSource("dataProvider_getLanguage") + void test_uriLocale(String requestUri, String defaultLocale, String expected) { + this.mockRequest.setRequestURI(requestUri); + this.initUrlLocaleInterceptor(Locale.forLanguageTag(defaultLocale), "/en/home"); + + assertEquals(expected, this.mockLocaleResolver.resolveLocale(this.mockRequest).getLanguage()); } - @Test - void test_uriEnPath() { - this.mockRequest.setRequestURI("/en/home"); - this.initUrlLocaleInterceptor(Locale.forLanguageTag("en")); + private static Stream dataProvider_getLanguage() { + return Stream.of( + // (String requestUri, String defaultLocale, String expected) + Arguments.of("/en/home", "en", "en"), + Arguments.of("/de/home", "de", "de"), + Arguments.of("/en/home/", "en", "en") + ); + } + + @ParameterizedTest + @MethodSource("dataProvider_getRedirectedUrl") + void test_redirect(String requestUri, String defaultLocale, String defaultRequestUri, String expected) { + this.mockRequest.setRequestURI(requestUri); + this.initUrlLocaleInterceptor(Locale.forLanguageTag(defaultLocale), defaultRequestUri); - assertEquals("en", this.mockLocaleResolver.resolveLocale(this.mockRequest).getLanguage()); + assertEquals(expected, this.mockedResponse.getRedirectedUrl()); + } + + private static Stream dataProvider_getRedirectedUrl() { + return Stream.of( + // (String requestUri, String defaultLocale, String defaultRequestUri, String expected) + Arguments.of("/en/home", "en", "/en/home", null), + Arguments.of("/it/home", "en", "/en/home", "/en/home"), + Arguments.of("/it/home/", "en", "/%s/home", "/en/home/"), + Arguments.of("/it/home/", "en", "/en/home", "/en/home/"), + Arguments.of("/it/home", "en-US", "/%s/home", "/en-us/home"), + Arguments.of("/it", "en", "/en/home", "/en/home"), + Arguments.of("/it/", "en", "/en/home", "/en/home"), + Arguments.of("/", "en", "/%s/home", "/en/home"), + Arguments.of("/", "en", "/%s/home/", "/en/home/"), + Arguments.of("/", "en", null, "/en") + ); } @Test void test_redirectIfNotSupportedLocaleInUri_checkReturn() { this.mockRequest.setRequestURI("/it/home"); - this.initUrlLocaleInterceptor(Locale.forLanguageTag("en")); + this.initUrlLocaleInterceptor(Locale.forLanguageTag("en"), "/en/home"); // In the case of a redirect, Request is not set for MockLocaleResolver. MockLocaleResolver.resolveLocale // throws a NullPointerException. This can be used to test if the response is set correctly and to abort @@ -71,41 +104,18 @@ void test_redirectIfNotSupportedLocaleInUri_checkReturn() { assertThrows(NullPointerException.class, () -> this.mockLocaleResolver.resolveLocale(this.mockRequest)); } - @Test - void test_redirectIfNotSupportedLocaleDefaultLocale() { - this.mockRequest.setRequestURI("/it/home"); - this.initUrlLocaleInterceptor(Locale.forLanguageTag("en")); - - assertEquals("/en/home", this.mockedResponse.getRedirectedUrl()); - } - - @Test - void test_redirectIfNotSupportedLocaleDefaultLocaleAndRegion() { - this.mockRequest.setRequestURI("/it/home"); - this.initUrlLocaleInterceptor(Locale.forLanguageTag("en-us")); - - assertEquals("/en-us/home", this.mockedResponse.getRedirectedUrl()); - } - - @Test - void test_redirectIfNotSupportedLocaleInUriWithOutPath() { - this.mockRequest.setRequestURI("/it"); - this.initUrlLocaleInterceptor(Locale.forLanguageTag("en")); - - assertEquals("/en/home", this.mockedResponse.getRedirectedUrl()); - } - @Test void test_fullUrl() { - this.mockRequest.setProtocol("https"); - this.mockRequest.setRemoteHost("www.example.com"); - this.mockRequest.setRemotePort(1234); - this.mockRequest.setRequestURI("/it/home"); - this.mockRequest.setQueryString("param1=value1¶m2=value2"); - this.initUrlLocaleInterceptor(Locale.forLanguageTag("en")); + URI uri = URI.create("https://www.example.com:1234/it/home?param1=value1¶m2=value2"); + this.mockRequest.setProtocol(uri.getScheme()); + this.mockRequest.setRemoteHost(uri.getHost()); + this.mockRequest.setRemotePort(uri.getPort()); + this.mockRequest.setRequestURI(uri.getPath()); + this.mockRequest.setQueryString(uri.getQuery()); + this.initUrlLocaleInterceptor(Locale.forLanguageTag("en"), "/en/home"); assertEquals( - "/en/homeparam1=value1¶m2=value2", + "/en/home?param1=value1¶m2=value2", this.mockedResponse.getRedirectedUrl() ); }