提交 8aeae49f 编写于 作者: R Rossen Stoyanchev

Support for servletPath prefix in ServletRequestPathUtils

Closes gh-26445
上级 f22e2ac5
......@@ -15,24 +15,29 @@
*/
package org.springframework.web.util;
import java.nio.charset.StandardCharsets;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.MappingMatch;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.RequestPath;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Utility class to parse the path of an {@link HttpServletRequest} to a
* {@link RequestPath} and cache it in a request attribute for further access.
* This can then be used for URL path matching with
* {@link org.springframework.web.util.pattern.PathPattern PathPattern}s.
*
* <p>Also includes helper methods to return either a previously
* {@link UrlPathHelper#resolveAndCacheLookupPath resolved} String lookupPath
* or a previously {@link #parseAndCache parsed} {@code RequestPath} depending
* on which is cached in request attributes.
* Utility class to assist with preparation and access to the lookup path for
* request mapping purposes. This can be the parsed {@link RequestPath}
* representation of the path when use of
* {@link org.springframework.web.util.pattern.PathPattern parsed patterns}
* is enabled or a String path for use with a
* {@link org.springframework.util.PathMatcher} otherwise.
*
* @author Rossen Stoyanchev
* @since 5.3
......@@ -44,20 +49,21 @@ public abstract class ServletRequestPathUtils {
/**
* Parse the {@link HttpServletRequest#getRequestURI() requestURI} of the
* request and its {@code contextPath} to create a {@link RequestPath} and
* cache it in the request attribute {@link #PATH_ATTRIBUTE}.
* Parse the {@link HttpServletRequest#getRequestURI() requestURI} to a
* {@link RequestPath} and save it in the request attribute
* {@link #PATH_ATTRIBUTE} for subsequent use with
* {@link org.springframework.web.util.pattern.PathPattern parsed patterns}.
* The returned {@code RequestPath} will have both the contextPath and any
* servletPath prefix omitted from the {@link RequestPath#pathWithinApplication()
* pathWithinApplication} it exposes.
*
* <p>This method ignores the {@link HttpServletRequest#getServletPath()
* servletPath} and the {@link HttpServletRequest#getPathInfo() pathInfo}.
* Therefore in case of a Servlet mapping by prefix, the
* {@link RequestPath#pathWithinApplication()} will always include the
* Servlet prefix.
* <p>This method is typically called by the {@code DispatcherServlet} to
* if any {@code HandlerMapping} indicates that it uses parsed patterns.
* After that the pre-parsed and cached {@code RequestPath} can be accessed
* through {@link #getParsedRequestPath(ServletRequest)}.
*/
public static RequestPath parseAndCache(HttpServletRequest request) {
String requestUri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE);
requestUri = (requestUri != null ? requestUri : request.getRequestURI());
RequestPath requestPath = RequestPath.parse(requestUri, request.getContextPath());
RequestPath requestPath = ServletRequestPath.parse(request);
request.setAttribute(PATH_ATTRIBUTE, requestPath);
return requestPath;
}
......@@ -172,4 +178,108 @@ public abstract class ServletRequestPathUtils {
request.getAttribute(UrlPathHelper.PATH_ATTRIBUTE) != null);
}
/**
* Simple wrapper around the default {@link RequestPath} implementation that
* supports a servletPath as an additional prefix to be omitted from
* {@link #pathWithinApplication()}.
*/
private static class ServletRequestPath implements RequestPath {
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 : "");
}
@Override
public String value() {
return this.requestPath.value();
}
@Override
public List<Element> elements() {
return this.requestPath.elements();
}
@Override
public PathContainer contextPath() {
return this.contextPath;
}
@Override
public PathContainer pathWithinApplication() {
return this.requestPath.pathWithinApplication();
}
@Override
public RequestPath modifyContextPath(String contextPath) {
throw new UnsupportedOperationException();
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
return (this.requestPath.equals(((ServletRequestPath) other).requestPath));
}
@Override
public int hashCode() {
return this.requestPath.hashCode();
}
@Override
public String toString() {
return this.requestPath.toString();
}
public static RequestPath parse(HttpServletRequest request) {
String requestUri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE);
if (requestUri == null) {
requestUri = request.getRequestURI();
}
if (UrlPathHelper.servlet4Present) {
String servletPathPrefix = Servlet4Delegate.getServletPathPrefix(request);
if (StringUtils.hasText(servletPathPrefix)) {
return new ServletRequestPath(requestUri, request.getContextPath(), servletPathPrefix);
}
}
return RequestPath.parse(requestUri, request.getContextPath());
}
}
/**
* Inner class to avoid a hard dependency on Servlet 4 {@link HttpServletMapping}
* and {@link MappingMatch} at runtime.
*/
private static class Servlet4Delegate {
@Nullable
public static String getServletPathPrefix(HttpServletRequest request) {
HttpServletMapping mapping = (HttpServletMapping) request.getAttribute(RequestDispatcher.INCLUDE_MAPPING);
if (mapping == null) {
mapping = request.getHttpServletMapping();
}
MappingMatch match = mapping.getMappingMatch();
if (!ObjectUtils.nullSafeEquals(match, MappingMatch.PATH)) {
return null;
}
String servletPath = (String) request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE);
servletPath = (servletPath != null ? servletPath : request.getServletPath());
return UriUtils.encodePath(servletPath, StandardCharsets.UTF_8);
}
}
}
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
......@@ -62,7 +62,7 @@ public class UrlPathHelper {
*/
public static final String PATH_ATTRIBUTE = UrlPathHelper.class.getName() + ".PATH";
private static final boolean servlet4Present =
static final boolean servlet4Present =
ClassUtils.hasMethod(HttpServletRequest.class, "getHttpServletMapping");
/**
......
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
......@@ -26,40 +26,37 @@ import static org.assertj.core.api.Assertions.assertThat;
class DefaultRequestPathTests {
@Test
void requestPath() {
void parse() {
// basic
testRequestPath("/app/a/b/c", "/app", "/a/b/c");
testParse("/app/a/b/c", "/app", "/a/b/c");
// no context path
testRequestPath("/a/b/c", "", "/a/b/c");
testParse("/a/b/c", "", "/a/b/c");
// context path only
testRequestPath("/a/b", "/a/b", "");
testParse("/a/b", "/a/b", "");
// root path
testRequestPath("/", "", "/");
testParse("/", "", "/");
// empty path
testRequestPath("", "", "");
testRequestPath("", "/", "");
testParse("", "", "");
testParse("", "/", "");
// trailing slash
testRequestPath("/app/a/", "/app", "/a/");
testRequestPath("/app/a//", "/app", "/a//");
testParse("/app/a/", "/app", "/a/");
testParse("/app/a//", "/app", "/a//");
}
private void testRequestPath(String fullPath, String contextPath, String pathWithinApplication) {
private void testParse(String fullPath, String contextPath, String pathWithinApplication) {
RequestPath requestPath = RequestPath.parse(fullPath, contextPath);
Object expected = contextPath.equals("/") ? "" : contextPath;
assertThat(requestPath.contextPath().value()).isEqualTo(expected);
assertThat(requestPath.pathWithinApplication().value()).isEqualTo(pathWithinApplication);
}
@Test
void updateRequestPath() {
void modifyContextPath() {
RequestPath requestPath = RequestPath.parse("/aA/bB/cC", null);
assertThat(requestPath.contextPath().value()).isEqualTo("");
......
/*
* Copyright 2002-2021 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.util;
import javax.servlet.http.MappingMatch;
import org.junit.jupiter.api.Test;
import org.springframework.http.server.RequestPath;
import org.springframework.web.testfixture.servlet.MockHttpServletMapping;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link ServletRequestPathUtils}.
* @author Rossen Stoyanchev
*/
public class ServletRequestPathUtilsTests {
@Test
void parseAndCache() {
// basic
testParseAndCache("/app/servlet/a/b/c", "/app", "/servlet", "/a/b/c");
// contextPath only, servletPathOnly, contextPath and servletPathOnly
testParseAndCache("/app/a/b/c", "/app", "", "/a/b/c");
testParseAndCache("/servlet/a/b/c", "", "/servlet", "/a/b/c");
testParseAndCache("/app1/app2/servlet1/servlet2", "/app1/app2", "/servlet1/servlet2", "");
// trailing slash
testParseAndCache("/app/servlet/a/", "/app", "/servlet", "/a/");
testParseAndCache("/app/servlet/a//", "/app", "/servlet", "/a//");
}
private void testParseAndCache(
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);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册