目录
1. Servlet 规范解读
1.1. Use of URL Paths
1.2. Specification of Mappings
2. Tomcat 源码分析
2.1. 几个概念
2.2. <url-pattern> 校验规则
2.3. <url-pattern> 如何分类
2.4. <url-pattern> 分类过程
2.5. <url-pattern> 匹配过程
3. 综合示例
1. Servlet 规范解读
1.1. Use of URL Paths
Upon receipt of a client request, the Web container determines the Web application to which to forward it. The Web application selected must have the longest context path that matches the start of the request URL.The matched part of the URL is the context path when mapping to servlets.
The Web container next must locate the servlet to process the request using the path mapping procedure described below.
The path used for mapping to a servlet is the request URL from the request object minus the context path and the path parameters. The URL path mapping rules below are used in order. The first successful match is used with no further matches attempted:
The container must use case-sensitive string comparisons for matching.
1.2. Specification of Mappings
In the Web application deployment descriptor, the following syntax is used to define mappings:
If the effective web.xml (after merging information from fragments and annotations) contains any url-patterns that are mapped to multiple servlets then the deployment must fail.
2. Tomcat 源码分析
2.1. 几个概念
在分析之前简单看下tomcat源码中的几个概念,Context、Wrapper、Servlet:
2.2. <url-pattern> 校验规则
org.apache.catalina.core.StandardContext.java:
/**
* Adjust the URL pattern to begin with a leading slash, if appropriate
* (i.e. we are running a servlet 2.2 application). Otherwise, return
* the specified URL pattern unchanged.
*
* @param urlPattern The URL pattern to be adjusted (if needed)
* and returned
* @return the URL pattern with a leading slash if needed
*/
protected String adjustURLPattern(String urlPattern) {
if (urlPattern == null)
return urlPattern;
if (urlPattern.startsWith("/") || urlPattern.startsWith("*."))
return urlPattern;
if (!isServlet22())
return urlPattern;
if(log.isDebugEnabled())
log.debug(sm.getString("standardContext.urlPattern.patternWarning",
urlPattern));
return "/" + urlPattern;
}
/**
* Validate the syntax of a proposed <code><url-pattern></code>
* for conformance with specification requirements.
*
* @param urlPattern URL pattern to be validated
* @return <code>true</code> if the URL pattern is conformant
*/
private boolean validateURLPattern(String urlPattern) {
if (urlPattern == null)
return false;
if (urlPattern.indexOf('\n') >= 0 || urlPattern.indexOf('\r') >= 0) {
return false;
}
if (urlPattern.equals("")) {
return true;
}
if (urlPattern.startsWith("*.")) {
if (urlPattern.indexOf('/') < 0) {
checkUnusualURLPattern(urlPattern);
return true;
} else
return false;
}
if ( (urlPattern.startsWith("/")) &&
(urlPattern.indexOf("*.") < 0)) {
checkUnusualURLPattern(urlPattern);
return true;
} else
return false;
}
/**
* Check for unusual but valid <code><url-pattern></code>s.
* See Bugzilla 34805, 43079 & 43080
*/
private void checkUnusualURLPattern(String urlPattern) {
if (log.isInfoEnabled()) {
// First group checks for '*' or '/foo*' style patterns
// Second group checks for *.foo.bar style patterns
if((urlPattern.endsWith("*") && (urlPattern.length() < 2 ||
urlPattern.charAt(urlPattern.length()-2) != '/')) ||
urlPattern.startsWith("*.") && urlPattern.length() > 2 &&
urlPattern.lastIndexOf('.') > 1) {
log.info("Suspicious url pattern: \"" + urlPattern + "\"" +
" in context [" + getName() + "] - see" +
" sections 12.1 and 12.2 of the Servlet specification");
}
}
}
2.3. <url-pattern> 如何分类
org.apache.catalina.mapper.Mapper.java#ContextVersion
protected static final class ContextVersion extends MapElement<Context> {
public final String path;
public final int slashCount;
public final WebResourceRoot resources;
public String[] welcomeResources;
public MappedWrapper defaultWrapper = null;
public MappedWrapper[] exactWrappers = new MappedWrapper[0];
public MappedWrapper[] wildcardWrappers = new MappedWrapper[0];
public MappedWrapper[] extensionWrappers = new MappedWrapper[0];
...
}
2.4. <url-pattern> 分类过程
org.apache.catalina.mapper.Mapper.java
/**
* Adds a wrapper to the given context.
*
* @param context The context to which to add the wrapper
* @param path Wrapper mapping
* @param wrapper The Wrapper object
* @param jspWildCard true if the wrapper corresponds to the JspServlet
* and the mapping path contains a wildcard; false otherwise
* @param resourceOnly true if this wrapper always expects a physical
* resource to be present (such as a JSP)
*/
protected void addWrapper(ContextVersion context, String path,
Wrapper wrapper, boolean jspWildCard, boolean resourceOnly) {
synchronized (context) {
if (path.endsWith("/*")) {
// Wildcard wrapper
String name = path.substring(0, path.length() - 2);
MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.wildcardWrappers;
MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1];
if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.wildcardWrappers = newWrappers;
int slashCount = slashCount(newWrapper.name);
if (slashCount > context.nesting) {
context.nesting = slashCount;
}
}
} else if (path.startsWith("*.")) {
// Extension wrapper
String name = path.substring(2);
MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.extensionWrappers;
MappedWrapper[] newWrappers =
new MappedWrapper[oldWrappers.length + 1];
if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.extensionWrappers = newWrappers;
}
} else if (path.equals("/")) {
// Default wrapper
MappedWrapper newWrapper = new MappedWrapper("", wrapper,
jspWildCard, resourceOnly);
context.defaultWrapper = newWrapper;
} else {
// Exact wrapper
final String name;
if (path.length() == 0) {
// Special case for the Context Root mapping which is
// treated as an exact match
name = "/";
} else {
name = path;
}
MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.exactWrappers;
MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1];
if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.exactWrappers = newWrappers;
}
}
}
}
2.5. <url-pattern> 匹配过程
org.apache.catalina.mapper.Mapper.java
/**
* Wrapper mapping.
* @throws IOException if the buffers are too small to hold the results of
* the mapping.
*/
private final void internalMapWrapper(ContextVersion contextVersion,
CharChunk path,
MappingData mappingData) throws IOException {
// ...
// Rule 1 -- Exact Match
MappedWrapper[] exactWrappers = contextVersion.exactWrappers;
internalMapExactWrapper(exactWrappers, path, mappingData);
// Rule 2 -- Prefix Match
boolean checkJspWelcomeFiles = false;
MappedWrapper[] wildcardWrappers = contextVersion.wildcardWrappers;
if (mappingData.wrapper == null) {
internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting,
path, mappingData);
// ...
}
// ...
// Rule 3 -- Extension Match
MappedWrapper[] extensionWrappers = contextVersion.extensionWrappers;
if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
internalMapExtensionWrapper(extensionWrappers, path, mappingData,
true);
}
// Rule 4 -- Welcome resources processing for servlets
if (mappingData.wrapper == null) {
boolean checkWelcomeFiles = checkJspWelcomeFiles;
// ...
}
// ...
// Rule 7 -- Default servlet
if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
if (contextVersion.defaultWrapper != null) {
mappingData.wrapper = contextVersion.defaultWrapper.object;
mappingData.requestPath.setChars
(path.getBuffer(), path.getStart(), path.getLength());
mappingData.wrapperPath.setChars
(path.getBuffer(), path.getStart(), path.getLength());
mappingData.matchType = MappingMatch.DEFAULT;
}
// ...
}
// ...
}
3. 综合示例
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>urlpattern</display-name>
<servlet>
<servlet-name>Servlet1</servlet-name>
<servlet-class>com.Servlet1</servlet-class>
</servlet>
<servlet>
<description></description>
<servlet-name>Servlet2</servlet-name>
<servlet-class>com.Servlet2</servlet-class>
</servlet>
<servlet>
<servlet-name>Servlet3</servlet-name>
<servlet-class>com.Servlet3</servlet-class>
</servlet>
<servlet>
<servlet-name>Servlet4</servlet-name>
<servlet-class>com.Servlet4</servlet-class>
</servlet>
<servlet>
<servlet-name>Servlet5</servlet-name>
<servlet-class>com.Servlet5</servlet-class>
</servlet>
<servlet>
<servlet-name>Servlet6</servlet-name>
<servlet-class>com.Servlet6</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Servlet1</servlet-name>
<url-pattern>/Servlet1</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Servlet2</servlet-name>
<url-pattern>/Servlet2/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Servlet6</servlet-name>
<url-pattern>/Servlet6/</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Servlet4</servlet-name>
<url-pattern>*.foo</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Servlet3</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Servlet5</servlet-name>
<url-pattern></url-pattern>
</servlet-mapping>
</web-app>
测试url:
http://localhost:8080/urlpattern/Servlet2
http://localhost:8080/urlpattern/Servlet2/a/b/c/d
http://localhost:8080/urlpattern/Servlet1
http://localhost:8080/urlpattern/Servlet6/
http://localhost:8080/urlpattern/ServletX/l/m/n/x.foo
http://localhost:8080/urlpattern/
http://localhost:8080/urlpattern
http://localhost:8080/urlpattern/Servlet6
http://localhost:8080/urlpattern/Servlet1/
http://localhost:8080/urlpattern/ServletY/NOT/EXIST/
测试结果:
参考:
JSR-000340 JavaTM Servlet 3.1 Final Release for Evaluation: https://download.oracle.com/otndocs/jcp/servlet-3_1-fr-eval-spec/index.html