Poison

关于 web.xml 中的通配符匹配问题

临近下班,帮同事查了个路径匹配的问题,在此简单记录。问题简化后如下,同事在 web.xml 中配置的路径匹配如下:

1
2
3
4
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/pc/*</url-pattern>
</servlet-mapping>

在 Spring MVC 中配置的注解为:@RequestMapping("/pc/**"),请求接口 /pc/index.htm 时服务器响应状态码 404,经过调试,调用链路依次为:

  • org.springframework.web.servlet.DispatcherServlet#doDispatch
  • org.springframework.web.servlet.DispatcherServlet#getHandler
  • org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
  • org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal
  • org.springframework.web.util.UrlPathHelper#getLookupPathForRequest
  • org.springframework.web.util.UrlPathHelper#getPathWithinServletMapping

关键的方法源码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* Return the path within the servlet mapping for the given request,
* i.e. the part of the request's URL beyond the part that called the servlet,
* or "" if the whole URL has been used to identify the servlet.
* <p>Detects include request URL if called within a RequestDispatcher include.
* <p>E.g.: servlet mapping = "/*"; request URI = "/test/a" -> "/test/a".
* <p>E.g.: servlet mapping = "/"; request URI = "/test/a" -> "/test/a".
* <p>E.g.: servlet mapping = "/test/*"; request URI = "/test/a" -> "/a".
* <p>E.g.: servlet mapping = "/test"; request URI = "/test" -> "".
* <p>E.g.: servlet mapping = "/*.test"; request URI = "/a.test" -> "".
* @param request current HTTP request
* @return the path within the servlet mapping, or ""
*/
public String getPathWithinServletMapping(HttpServletRequest request) {
String pathWithinApp = getPathWithinApplication(request);
String servletPath = getServletPath(request);
String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);
String path;

// If the app container sanitized the servletPath, check against the sanitized version
if (servletPath.contains(sanitizedPathWithinApp)) {
path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);
}
else {
path = getRemainingPath(pathWithinApp, servletPath, false);
}

if (path != null) {
// Normal case: URI contains servlet path.
return path;
}
else {
// Special case: URI is different from servlet path.
String pathInfo = request.getPathInfo();
if (pathInfo != null) {
// Use path info if available. Indicates index page within a servlet mapping?
// e.g. with index page: URI="/", servletPath="/index.html"
return pathInfo;
}
if (!this.urlDecode) {
// No path info... (not mapped by prefix, nor by extension, nor "/*")
// For the default servlet mapping (i.e. "/"), urlDecode=false can
// cause issues since getServletPath() returns a decoded path.
// If decoding pathWithinApp yields a match just use pathWithinApp.
path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false);
if (path != null) {
return pathWithinApp;
}
}
// Otherwise, use the full servlet path.
return servletPath;
}
}

根据注释即可看出在我们的场景中到 Spring MVC 去查询 handler 方法使用的 path 为 index.htm,导致匹配不到对应的 handler 方法。