首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Tomcat】:深入理解 <url-pattern>

【Tomcat】:深入理解 <url-pattern>

作者头像
WEBJ2EE
发布2020-12-16 14:37:22
发布2020-12-16 14:37:22
67700
代码可运行
举报
文章被收录于专栏:WebJ2EEWebJ2EE
运行总次数:0
代码可运行
代码语言:javascript
代码运行次数:0
运行
复制
目录
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:

  1. The container will try to find an exact match of the path of the request to the path of the servlet. A successful match selects the servlet.
  2. The container will recursively try to match the longest path-prefix. This is done by stepping down the path tree a directory at a time, using the ’/’ character as a path separator. The longest match determines the servlet selected.
  3. If the last segment in the URL path contains an extension (e.g. .jsp), the servlet container will try to match a servlet that handles requests for the extension. An extension is defined as the part of the last segment after the last ’.’ character.
  4. If neither of the previous three rules result in a servlet match, the container will attempt to serve content appropriate for the resource requested. If a "default" servlet is defined for the application, it will be used. Many containers provide an implicit default servlet for serving content.

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:

  • A string beginning with a ‘/’ character and ending with a ‘/*’ suffix is used for path mapping.
  • A string beginning with a ‘*.’ prefix is used as an extension mapping.
  • The empty string ("") is a special URL pattern that exactly maps to the application's context root, i.e., requests of the form http://host:port/<contextroot>/. In this case the path info is ’/’ and the servlet path and context path is empty string (““).
  • A string containing only the ’/’ character indicates the "default" servlet of the application. In this case the servlet path is the request URI minus the context path and the path info is null.
  • All other strings are used for exact matches only.

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:

  • Servlet:这个很清楚,就是用来处理业务请求。
  • Wrapper:Servlet 和映射的结合,具体点就是web.xml中配置的servlet信息。
  • Context:表示一个应用,包含了 web.xml 中配置的所有信息,当一个请求到来时,它负责找到对应的Servlet,然后调用这个 Servlet 的 service 方法,执行我们所写的业务逻辑。

2.2. <url-pattern> 校验规则

org.apache.catalina.core.StandardContext.java:

代码语言:javascript
代码运行次数:0
运行
复制
/**
 * 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>&lt;url-pattern&gt;</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>&lt;url-pattern&gt;</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

代码语言:javascript
代码运行次数:0
运行
复制
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

代码语言:javascript
代码运行次数:0
运行
复制
/**
 * 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;
            }
        }
    }
}
  • /* 结尾:wildcardWrappers
  • *. 开头:extensionWrappers
  • /:defaultWrapper
  • 其他:extractWrappers
    • 注:包含 "" -> Context Root mapping

2.5. <url-pattern> 匹配过程

org.apache.catalina.mapper.Mapper.java

代码语言:javascript
代码运行次数:0
运行
复制
/**
 * 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;
        }
        // ...
    }
    // ...
}
  • 规则1:精确匹配,使用 contextVersion 的 exactWrappers
  • 规则2:前缀匹配,使用 contextVersion 的 wildcardWrappers
  • 规则3:扩展名匹配,使用 contextVersion 的 extensionWrappers
  • 规则4:欢迎页匹配,使用 contextVersion 的welcomeResources
  • 规则7:使用默认的servlet,使用contextVersion的defaultWrapper

3. 综合示例

web.xml:

代码语言:javascript
代码运行次数:0
运行
复制
<?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:

代码语言:javascript
代码运行次数:0
运行
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档