Skip to content

Commit

Permalink
Add support for changing context path in ServletRequestPath
Browse files Browse the repository at this point in the history
This commit implements modifyContextPath in ServletRequestPath and
apply the same logic of concatenating the servlet path with the
context path.

Closes gh-33251
  • Loading branch information
snicoll committed Aug 7, 2024
1 parent 9b85a24 commit 76b2d13
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 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.
Expand Down Expand Up @@ -41,6 +41,7 @@
* {@link org.springframework.util.PathMatcher} otherwise.
*
* @author Rossen Stoyanchev
* @author Stephane Nicoll
* @since 5.3
*/
public abstract class ServletRequestPathUtils {
Expand Down Expand Up @@ -186,14 +187,16 @@ public static boolean hasCachedPath(ServletRequest request) {
*/
private static final class ServletRequestPath implements RequestPath {

private final PathElements pathElements;

private final RequestPath requestPath;

private final PathContainer contextPath;

private ServletRequestPath(String rawPath, @Nullable String contextPath, String servletPathPrefix) {
Assert.notNull(servletPathPrefix, "`servletPathPrefix` is required");
this.requestPath = RequestPath.parse(rawPath, contextPath + servletPathPrefix);
this.contextPath = PathContainer.parsePath(StringUtils.hasText(contextPath) ? contextPath : "");
private ServletRequestPath(PathElements pathElements) {
this.pathElements = pathElements;
this.requestPath = pathElements.createRequestPath();
this.contextPath = pathElements.createContextPath();
}

@Override
Expand All @@ -218,7 +221,7 @@ public PathContainer pathWithinApplication() {

@Override
public RequestPath modifyContextPath(String contextPath) {
throw new UnsupportedOperationException();
return new ServletRequestPath(this.pathElements.withContextPath(contextPath));
}


Expand Down Expand Up @@ -249,7 +252,7 @@ public static RequestPath parse(HttpServletRequest request) {
requestUri = (requestUri != null ? requestUri : request.getRequestURI());
String servletPathPrefix = getServletPathPrefix(request);
return (StringUtils.hasText(servletPathPrefix) ?
new ServletRequestPath(requestUri, request.getContextPath(), servletPathPrefix) :
new ServletRequestPath(new PathElements(requestUri, request.getContextPath(), servletPathPrefix)) :
RequestPath.parse(requestUri, request.getContextPath()));
}

Expand All @@ -265,6 +268,38 @@ private static String getServletPathPrefix(HttpServletRequest request) {
}
return null;
}

record PathElements(String rawPath, @Nullable String contextPath, String servletPathPrefix) {

PathElements {
Assert.notNull(servletPathPrefix, "`servletPathPrefix` is required");
}

private RequestPath createRequestPath() {
return RequestPath.parse(this.rawPath, this.contextPath + this.servletPathPrefix);
}

private PathContainer createContextPath() {
return PathContainer.parsePath(StringUtils.hasText(this.contextPath) ? this.contextPath : "");
}

PathElements withContextPath(String contextPath) {
if (!contextPath.startsWith("/") || contextPath.endsWith("/")) {
throw new IllegalArgumentException("Invalid contextPath '" + contextPath + "': " +
"must start with '/' and not end with '/'");
}
String contextPathToUse = this.servletPathPrefix + contextPath;
if (StringUtils.hasText(this.contextPath())) {
throw new IllegalStateException("Could not change context path to '" + contextPathToUse +
"': a context path is already specified");
}
if (!this.rawPath.startsWith(contextPathToUse)) {
throw new IllegalArgumentException("Invalid contextPath '" + contextPathToUse + "': " +
"must match the start of requestPath: '" + this.rawPath + "'");
}
return new PathElements(this.rawPath, contextPathToUse, "");
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;

/**
* Tests for {@link ServletRequestPathUtils}.
*
* @author Rossen Stoyanchev
* @author Stephane Nicoll
*/
class ServletRequestPathUtilsTests {

Expand All @@ -47,19 +50,63 @@ void parseAndCache() {
testParseAndCache("/app/servlet/a//", "/app", "/servlet", "/a//");
}

@Test
void modifyPathContextWithExistingContextPath() {
RequestPath requestPath = createRequestPath("/app/api/persons/42", "/app", "/api", "/persons/42");
assertThatIllegalStateException().isThrownBy(() -> requestPath.modifyContextPath("/persons"))
.withMessage("Could not change context path to '/api/persons': a context path is already specified");
}

@Test
void modifyPathContextWhenContextPathIsNotInThePath() {
RequestPath requestPath = createRequestPath("/api/persons/42", "", "/api", "/persons/42");
assertThatIllegalArgumentException().isThrownBy(() -> requestPath.modifyContextPath("/something"))
.withMessage("Invalid contextPath '/api/something': " +
"must match the start of requestPath: '/api/persons/42'");
}

@Test
void modifyPathContextReplacesServletPath() {
RequestPath requestPath = createRequestPath("/api/persons/42", "", "/api", "/persons/42");
RequestPath updatedRequestPath = requestPath.modifyContextPath("/persons");
assertThat(updatedRequestPath.contextPath().value()).isEqualTo("/api/persons");
assertThat(updatedRequestPath.pathWithinApplication().value()).isEqualTo("/42");
assertThat(updatedRequestPath.value()).isEqualTo("/api/persons/42");
}

@Test
void modifyPathContextWithContextPathNotStartingWithSlash() {
RequestPath requestPath = createRequestPath("/api/persons/42", "", "/api", "/persons/42");
assertThatIllegalArgumentException().isThrownBy(() -> requestPath.modifyContextPath("persons"))
.withMessage("Invalid contextPath 'persons': must start with '/' and not end with '/'");
}

@Test
void modifyPathContextWithContextPathEndingWithSlash() {
RequestPath requestPath = createRequestPath("/api/persons/42", "", "/api", "/persons/42");
assertThatIllegalArgumentException().isThrownBy(() -> requestPath.modifyContextPath("/persons/"))
.withMessage("Invalid contextPath '/persons/': must start with '/' and not end with '/'");
}

private void testParseAndCache(
String requestUri, String contextPath, String servletPath, String pathWithinApplication) {

RequestPath requestPath = createRequestPath(requestUri, contextPath, servletPath, pathWithinApplication);

assertThat(requestPath.contextPath().value()).isEqualTo(contextPath);
assertThat(requestPath.pathWithinApplication().value()).isEqualTo(pathWithinApplication);
}

private static RequestPath createRequestPath(
String requestUri, String contextPath, String servletPath, String pathWithinApplication) {

MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
request.setContextPath(contextPath);
request.setServletPath(servletPath);
request.setHttpServletMapping(new MockHttpServletMapping(
pathWithinApplication, contextPath, "myServlet", MappingMatch.PATH));

RequestPath requestPath = ServletRequestPathUtils.parseAndCache(request);

assertThat(requestPath.contextPath().value()).isEqualTo(contextPath);
assertThat(requestPath.pathWithinApplication().value()).isEqualTo(pathWithinApplication);
return ServletRequestPathUtils.parseAndCache(request);
}

}

0 comments on commit 76b2d13

Please sign in to comment.