Skip to content

Commit

Permalink
Release 0.3.0
Browse files Browse the repository at this point in the history
- Fix: Request with QueryString
- Handling trailing slash in RequestURI
  • Loading branch information
alaugks committed Jul 1, 2024
1 parent 3822fcf commit d8ad119
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 71 deletions.
24 changes: 10 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/).
Expand All @@ -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) |

Expand All @@ -32,14 +33,14 @@ An example in action can be seen [here](https://spring-boot-xliff-example.alaugk
<dependency>
<groupId>io.github.alaugks</groupId>
<artifactId>spring-requesturi-locale-interceptor</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
</dependency>
```

### 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'
```


Expand Down Expand Up @@ -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");
}
}
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.alaugks</groupId>
<artifactId>spring-requesturi-locale-interceptor</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
<packaging>jar</packaging>

<name>${project.groupId}:${project.artifactId}</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class RequestURILocaleInterceptor implements HandlerInterceptor {
private static final String PATH_DELIMITER = "/";
private final Locale defaultLocale;
private final List<Locale> supportedLocales;
private final String defaultHomePath;
private String defaultHomePath;

public RequestURILocaleInterceptor(Builder builder) {
this.defaultLocale = builder.defaultLocale;
Expand Down Expand Up @@ -59,25 +59,34 @@ public RequestURILocaleInterceptor build() {
this.supportedLocales = new ArrayList<>();
}

if (this.defaultRequestURI == null) {
this.defaultRequestURI = PATH_DELIMITER + this.defaultLocale;
}

return new RequestURILocaleInterceptor(this);
}
}

@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
Expand All @@ -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;
Expand All @@ -104,15 +113,15 @@ 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)
);

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));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,77 +40,82 @@ 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<Arguments> 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<Arguments> 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
// processing in RequestURILocaleInterceptor with a return false.
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&param2=value2");
this.initUrlLocaleInterceptor(Locale.forLanguageTag("en"));
URI uri = URI.create("https://www.example.com:1234/it/home?param1=value1&param2=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&param2=value2",
"/en/home?param1=value1&param2=value2",
this.mockedResponse.getRedirectedUrl()
);
}
Expand Down

0 comments on commit d8ad119

Please sign in to comment.