Tomcat、Servlet路径分析

综述

目前微服务大行其道,各种中台应运而生,中台处理部分业务时需要透传请求,这就涉及到对url的处理。一般透传的接口都会保持path路径不变,因此需要从前端的url中提取公共部分。这就需要理解Context Path、servlet path、path info,目前servlet提供了对应的方法去方便获取它们。不过这3个到底对应url中的那一段,还是需要好好研究一下,不然很可能得到错误的url。
可以肯定的是 request uri = Context Path + servlet path + path info

Context Path

There is one context per “web application” per Java Virtual Machine. (A “web application” is a collection of servlets and content installed under a specific subset of the server’s URL namespace such as /catalog and possibly installed via a .war file.)

上述是tomcat中ServletContext类注释的一部分,文中表示Context代表一个app,对应以前一个war包或者一个spring boot开发的一个微服务。

  1. 如果是以前的war包,那么Context-Path就是以前war包解压后的目录名。
  2. 如果是现在流行的,使用spring boot开发的应用(内嵌tomcat)。那么Context-Path由配置项server.servlet.context-path决定。如果不配置的话,默认为空;那么 request uri = Context Path + servlet path + path info

servlet path

url去掉Context Path后,剩余的部分用于匹配servlet。servlet的匹配规则可以通过如下方式进行匹配。

1、web.xml中的url-pattern指定(以前war包的模式)

<servlet-mapping>
    <servlet-name>ceshiServletservlet-name>
    <url-pattern>/ceshi/*url-pattern>
servlet-mapping>

2、通过java代码进行配置。

@Bean
public ServletRegistrationBean testdispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
    ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet,
                                                                       "/ceshi/*");
    registration.setName("ceshi");
    return registration;
}

url-pattern的配置规则

匹配优先级如下所示:

  1. 通配符:只能以"/*“结尾,比如”/ceshi/*";其他地方的“*”不会当做特殊字符处理
  2. 扩展名:只能是*.xx,比如*.jsp
  3. 默认匹配:servlet-url-pattern等于“/”
  4. 精确匹配:非上述场景,匹配的时候对比完整的url(剔除context-path余下的部分)。

上述匹配规则,可以直接通过tomcat源码得到印证:org.apache.catalina.mapper.Mapper#addWrapper(org.apache.catalina.mapper.Mapper.ContextVersion, java.lang.String, org.apache.catalina.Wrapper, boolean, boolean)

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);
            ... ...
        } else if (path.startsWith("*.")) {		// 以 *. 开头
            // Extension wrapper
            String name = path.substring(2);
            ... ...
        } 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;
            }
            ... ...
        }
    }
}

servlet path、path info的拆分

实际上对于servlet容器来说,他并不关心path info,只要通过url(剔除context-path余下的部分)匹配上servlet,并将请求委托给其处理后,剩余的流程就和servlet容器无关了。因此它本身是用不到path info这部分的。path info只是为了servlet容器方便servlet框架(spring mvc、struts等)做进一步请求转发设计的(不可能为每个业务请求写一个servlet)。

将url(剔除context-path余下的部分)拆分成servlet path、path info也是分上述场景。

匹配的优先级如下:

  1. 精确匹配
  2. 通配符匹配
  3. 扩展名匹配
  4. 欢迎页面匹配
  5. 默认匹配

servlet容器通过url匹配servlet的流程见org.apache.catalina.mapper.Mapper#internalMapWrapper

1、精确匹配

servlet path = 配置的url-pattern

path info = null

因为是精确匹配,所以url(剔除context-path余下的部分)必须等于servlet的url-pattern;因此path info=null。

private final void internalMapExactWrapper
    (MappedWrapper[] wrappers, CharChunk path, MappingData mappingData) {
    MappedWrapper wrapper = exactFind(wrappers, path);
    if (wrapper != null) {

        mappingData.requestPath.setString(wrapper.name);
        mappingData.wrapper = wrapper.object;
        // 特殊场景
        if (path.equals("/")) {
            // Special handling for Context Root mapped servlet
            mappingData.pathInfo.setString("/");
            mappingData.wrapperPath.setString("");
            // This seems wrong but it is what the spec says...
            mappingData.contextPath.setString("");
            mappingData.matchType = MappingMatch.CONTEXT_ROOT;
        } else {
            // HttpServletRequest#getServletPath方法最终取的是mappingData.wrapperPath
            mappingData.wrapperPath.setString(wrapper.name);
            mappingData.matchType = MappingMatch.EXACT;
        }
    }
}

2、通配符匹配

servlet path = 配置的url-pattern去掉结尾的通配符;比如url-pattern为“/ceshi/*”,那么servlet path = “/ceshi”;

path info = 原始url剔除context-path和servlet path后剩下的部分

源码见org.apache.catalina.mapper.Mapper#internalMapWildcardWrapper

private final void internalMapWildcardWrapper
    (MappedWrapper[] wrappers, int nesting, CharChunk path,
     MappingData mappingData) {
    // path代表去掉context-path后的url
    int pathEnd = path.getEnd();

    int lastSlash = -1;
    int length = -1;
    int pos = find(wrappers, path);
    if (pos != -1) {
        ... ...
        // wrappers[pos].name就是配置的url-pattern去掉结尾的/\*
        length = wrappers[pos].name.length();
        if (found) {
            mappingData.wrapperPath.setString(wrappers[pos].name);

            # 如果path的长度大于wrappers[pos].name的长度,那么url中剩的一节就作为path info。
                if (path.getLength() > length) {
                    # HttpServletRequest#getPathInfo最终来自 mappingData.pathInfo
                        mappingData.pathInfo.setChars
                        (path.getBuffer(),
                         path.getOffset() + length,
                         path.getLength() - length);
                }
            mappingData.requestPath.setChars
                (path.getBuffer(), path.getOffset(), path.getLength());
            mappingData.wrapper = wrappers[pos].object;
            mappingData.jspWildCard = wrappers[pos].jspWildCard;
            mappingData.matchType = MappingMatch.PATH;
        }
    }
}

3、默认servlet

servlet path = 剔除context-path后剩余的url

path info = null

详细见org.apache.catalina.mapper.Mapper#internalMapWrapper

// 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());
        // 直接使用剔除context-path后的url作为servlet path,path info为null
        mappingData.wrapperPath.setChars(path.getBuffer(), path.getStart(), path.getLength());
        mappingData.matchType = MappingMatch.DEFAULT;
    }

你可能感兴趣的:(tomcat,servlet,firefox)