Skip to content

Commit 76b2d13

Browse files
committed
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
1 parent 9b85a24 commit 76b2d13

File tree

2 files changed

+93
-11
lines changed

2 files changed

+93
-11
lines changed

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

+42-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -41,6 +41,7 @@
4141
* {@link org.springframework.util.PathMatcher} otherwise.
4242
*
4343
* @author Rossen Stoyanchev
44+
* @author Stephane Nicoll
4445
* @since 5.3
4546
*/
4647
public abstract class ServletRequestPathUtils {
@@ -186,14 +187,16 @@ public static boolean hasCachedPath(ServletRequest request) {
186187
*/
187188
private static final class ServletRequestPath implements RequestPath {
188189

190+
private final PathElements pathElements;
191+
189192
private final RequestPath requestPath;
190193

191194
private final PathContainer contextPath;
192195

193-
private ServletRequestPath(String rawPath, @Nullable String contextPath, String servletPathPrefix) {
194-
Assert.notNull(servletPathPrefix, "`servletPathPrefix` is required");
195-
this.requestPath = RequestPath.parse(rawPath, contextPath + servletPathPrefix);
196-
this.contextPath = PathContainer.parsePath(StringUtils.hasText(contextPath) ? contextPath : "");
196+
private ServletRequestPath(PathElements pathElements) {
197+
this.pathElements = pathElements;
198+
this.requestPath = pathElements.createRequestPath();
199+
this.contextPath = pathElements.createContextPath();
197200
}
198201

199202
@Override
@@ -218,7 +221,7 @@ public PathContainer pathWithinApplication() {
218221

219222
@Override
220223
public RequestPath modifyContextPath(String contextPath) {
221-
throw new UnsupportedOperationException();
224+
return new ServletRequestPath(this.pathElements.withContextPath(contextPath));
222225
}
223226

224227

@@ -249,7 +252,7 @@ public static RequestPath parse(HttpServletRequest request) {
249252
requestUri = (requestUri != null ? requestUri : request.getRequestURI());
250253
String servletPathPrefix = getServletPathPrefix(request);
251254
return (StringUtils.hasText(servletPathPrefix) ?
252-
new ServletRequestPath(requestUri, request.getContextPath(), servletPathPrefix) :
255+
new ServletRequestPath(new PathElements(requestUri, request.getContextPath(), servletPathPrefix)) :
253256
RequestPath.parse(requestUri, request.getContextPath()));
254257
}
255258

@@ -265,6 +268,38 @@ private static String getServletPathPrefix(HttpServletRequest request) {
265268
}
266269
return null;
267270
}
271+
272+
record PathElements(String rawPath, @Nullable String contextPath, String servletPathPrefix) {
273+
274+
PathElements {
275+
Assert.notNull(servletPathPrefix, "`servletPathPrefix` is required");
276+
}
277+
278+
private RequestPath createRequestPath() {
279+
return RequestPath.parse(this.rawPath, this.contextPath + this.servletPathPrefix);
280+
}
281+
282+
private PathContainer createContextPath() {
283+
return PathContainer.parsePath(StringUtils.hasText(this.contextPath) ? this.contextPath : "");
284+
}
285+
286+
PathElements withContextPath(String contextPath) {
287+
if (!contextPath.startsWith("/") || contextPath.endsWith("/")) {
288+
throw new IllegalArgumentException("Invalid contextPath '" + contextPath + "': " +
289+
"must start with '/' and not end with '/'");
290+
}
291+
String contextPathToUse = this.servletPathPrefix + contextPath;
292+
if (StringUtils.hasText(this.contextPath())) {
293+
throw new IllegalStateException("Could not change context path to '" + contextPathToUse +
294+
"': a context path is already specified");
295+
}
296+
if (!this.rawPath.startsWith(contextPathToUse)) {
297+
throw new IllegalArgumentException("Invalid contextPath '" + contextPathToUse + "': " +
298+
"must match the start of requestPath: '" + this.rawPath + "'");
299+
}
300+
return new PathElements(this.rawPath, contextPathToUse, "");
301+
}
302+
}
268303
}
269304

270305
}

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

+51-4
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@
2424
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
2525

2626
import static org.assertj.core.api.Assertions.assertThat;
27+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
28+
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
2729

2830
/**
2931
* Tests for {@link ServletRequestPathUtils}.
3032
*
3133
* @author Rossen Stoyanchev
34+
* @author Stephane Nicoll
3235
*/
3336
class ServletRequestPathUtilsTests {
3437

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

53+
@Test
54+
void modifyPathContextWithExistingContextPath() {
55+
RequestPath requestPath = createRequestPath("/app/api/persons/42", "/app", "/api", "/persons/42");
56+
assertThatIllegalStateException().isThrownBy(() -> requestPath.modifyContextPath("/persons"))
57+
.withMessage("Could not change context path to '/api/persons': a context path is already specified");
58+
}
59+
60+
@Test
61+
void modifyPathContextWhenContextPathIsNotInThePath() {
62+
RequestPath requestPath = createRequestPath("/api/persons/42", "", "/api", "/persons/42");
63+
assertThatIllegalArgumentException().isThrownBy(() -> requestPath.modifyContextPath("/something"))
64+
.withMessage("Invalid contextPath '/api/something': " +
65+
"must match the start of requestPath: '/api/persons/42'");
66+
}
67+
68+
@Test
69+
void modifyPathContextReplacesServletPath() {
70+
RequestPath requestPath = createRequestPath("/api/persons/42", "", "/api", "/persons/42");
71+
RequestPath updatedRequestPath = requestPath.modifyContextPath("/persons");
72+
assertThat(updatedRequestPath.contextPath().value()).isEqualTo("/api/persons");
73+
assertThat(updatedRequestPath.pathWithinApplication().value()).isEqualTo("/42");
74+
assertThat(updatedRequestPath.value()).isEqualTo("/api/persons/42");
75+
}
76+
77+
@Test
78+
void modifyPathContextWithContextPathNotStartingWithSlash() {
79+
RequestPath requestPath = createRequestPath("/api/persons/42", "", "/api", "/persons/42");
80+
assertThatIllegalArgumentException().isThrownBy(() -> requestPath.modifyContextPath("persons"))
81+
.withMessage("Invalid contextPath 'persons': must start with '/' and not end with '/'");
82+
}
83+
84+
@Test
85+
void modifyPathContextWithContextPathEndingWithSlash() {
86+
RequestPath requestPath = createRequestPath("/api/persons/42", "", "/api", "/persons/42");
87+
assertThatIllegalArgumentException().isThrownBy(() -> requestPath.modifyContextPath("/persons/"))
88+
.withMessage("Invalid contextPath '/persons/': must start with '/' and not end with '/'");
89+
}
90+
5091
private void testParseAndCache(
5192
String requestUri, String contextPath, String servletPath, String pathWithinApplication) {
5293

94+
RequestPath requestPath = createRequestPath(requestUri, contextPath, servletPath, pathWithinApplication);
95+
96+
assertThat(requestPath.contextPath().value()).isEqualTo(contextPath);
97+
assertThat(requestPath.pathWithinApplication().value()).isEqualTo(pathWithinApplication);
98+
}
99+
100+
private static RequestPath createRequestPath(
101+
String requestUri, String contextPath, String servletPath, String pathWithinApplication) {
102+
53103
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
54104
request.setContextPath(contextPath);
55105
request.setServletPath(servletPath);
56106
request.setHttpServletMapping(new MockHttpServletMapping(
57107
pathWithinApplication, contextPath, "myServlet", MappingMatch.PATH));
58108

59-
RequestPath requestPath = ServletRequestPathUtils.parseAndCache(request);
60-
61-
assertThat(requestPath.contextPath().value()).isEqualTo(contextPath);
62-
assertThat(requestPath.pathWithinApplication().value()).isEqualTo(pathWithinApplication);
109+
return ServletRequestPathUtils.parseAndCache(request);
63110
}
64111

65112
}

0 commit comments

Comments
 (0)