发布时间:2025-12-09 12:02:59 浏览次数:1
目录1. Servlet 规范解读 1.1. Use of URL Paths 1.2. Specification of Mappings2. 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" 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/Servlet2http://localhost:8080/urlpattern/Servlet2/a/b/c/dhttp://localhost:8080/urlpattern/Servlet1http://localhost:8080/urlpattern/Servlet6/http://localhost:8080/urlpattern/ServletX/l/m/n/x.foohttp://localhost:8080/urlpattern/http://localhost:8080/urlpatternhttp://localhost:8080/urlpattern/Servlet6http://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