Browse Source

Add support for changing context path in ServletRequestPath

This commit implements modifyContextPath in ServletRequestPath and
apply the same logic of concatenating the servlet path with the
context path.

Closes gh-33251
pull/33365/head
Stéphane Nicoll 1 year ago
parent
commit
76b2d13b2c
  1. 49
      spring-web/src/main/java/org/springframework/web/util/ServletRequestPathUtils.java
  2. 55
      spring-web/src/test/java/org/springframework/web/util/ServletRequestPathUtilsTests.java

49
spring-web/src/main/java/org/springframework/web/util/ServletRequestPathUtils.java

@ -1,5 +1,5 @@ @@ -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.
@ -41,6 +41,7 @@ import org.springframework.util.StringUtils; @@ -41,6 +41,7 @@ import org.springframework.util.StringUtils;
* {@link org.springframework.util.PathMatcher} otherwise.
*
* @author Rossen Stoyanchev
* @author Stephane Nicoll
* @since 5.3
*/
public abstract class ServletRequestPathUtils {
@ -186,14 +187,16 @@ public abstract class ServletRequestPathUtils { @@ -186,14 +187,16 @@ public abstract class ServletRequestPathUtils {
*/
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
@ -218,7 +221,7 @@ public abstract class ServletRequestPathUtils { @@ -218,7 +221,7 @@ public abstract class ServletRequestPathUtils {
@Override
public RequestPath modifyContextPath(String contextPath) {
throw new UnsupportedOperationException();
return new ServletRequestPath(this.pathElements.withContextPath(contextPath));
}
@ -249,7 +252,7 @@ public abstract class ServletRequestPathUtils { @@ -249,7 +252,7 @@ public abstract class ServletRequestPathUtils {
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()));
}
@ -265,6 +268,38 @@ public abstract class ServletRequestPathUtils { @@ -265,6 +268,38 @@ public abstract class ServletRequestPathUtils {
}
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, "");
}
}
}
}

55
spring-web/src/test/java/org/springframework/web/util/ServletRequestPathUtilsTests.java

@ -24,11 +24,14 @@ import org.springframework.web.testfixture.servlet.MockHttpServletMapping; @@ -24,11 +24,14 @@ import org.springframework.web.testfixture.servlet.MockHttpServletMapping;
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 {
@ -47,19 +50,63 @@ class ServletRequestPathUtilsTests { @@ -47,19 +50,63 @@ class ServletRequestPathUtilsTests {
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);
}
}

Loading…
Cancel
Save