Tomcat源码分析(五):请求映射

本文深入探讨Tomcat的请求映射过程,从Host和Context的映射,Servlet的精确匹配、前缀匹配和扩展匹配,到映射的建立,详细解析了请求如何在Tomcat内部被正确路由。
摘要由CSDN通过智能技术生成

请求映射

从上面的文章,我们知道,当请求经过Adapter处理之后,会调用从容器的pipeline中获取第一个Valve,调用其invoke方法,从而将请求交给容器进行处理
最上层容器是StandardEngine,由其Basic Valve将请求交给下层多个Host容器中的一个,首先看下其Basic Valve是哪个类

public StandardEngine() {
   

    super();
    // 添加基础Valve
    pipeline.setBasic(new StandardEngineValve());
    /* Set the jmvRoute using the system property jvmRoute */
    try {
   
        setJvmRoute(System.getProperty("jvmRoute"));
    } catch(Exception ex) {
   
        log.warn(sm.getString("standardEngine.jvmRouteFail"));
    }
    // By default, the engine will hold the reloading thread
    backgroundProcessorDelay = 10;

}

可以看到这里的Basic Valve是StandardEngineValve,接下来看下其invoke方法

public final void invoke(Request request, Response response)
        throws IOException, ServletException {
   

   // Select the Host to be used for this Request
   Host host = request.getHost();
   if (host == null) {
   
       // HTTP 0.9 or HTTP 1.0 request without a host when no default host
       // is defined.
       // Don't overwrite an existing error
       if (!response.isError()) {
   
           response.sendError(404);
       }
       return;
   }
   if (request.isAsyncSupported()) {
   
       request.setAsyncSupported(host.getPipeline().isAsyncSupported());
   }

   // Ask this Host to process this request
   host.getPipeline().getFirst().invoke(request, response);
}

这里做的主要就是从Request对象中获取Host容器,然后执行该容器的Pipeline
接下来看下Request.getHost

public Host getHost() {
   
    return mappingData.host;
}

可以看出,并没有进行什么计算,只是从MappingData这个属性中获取host,因此可以断定请求映射并不是发生在容器端
那么一定是发生在Connector端,从上篇文章知道,Processor负责生成Request对象,所以我们回过头看下那里的处理逻辑

执行映射

这里看下CoyoteAdapter的service方法中的这一行代码

postParseSuccess = postParseRequest(req, request, res, response);
protected boolean postParseRequest(org.apache.coyote.Request req, Request request,
            org.apache.coyote.Response res, Response response) throws IOException, ServletException {
   
            // 省略部分代码
            connector.getService().getMapper().map(serverName, decodedURI,
                    version, request.getMappingData());
}

接着看下map方法

public void map(MessageBytes host, MessageBytes uri, String version,
                    MappingData mappingData) throws IOException {
   

	// 请求的域名
    if (host.isNull()) {
   
        String defaultHostName = this.defaultHostName;
        if (defaultHostName == null) {
   
            return;
        }
        host.getCharChunk().append(defaultHostName);
    }
    // 将请求的域名转换为char数组
    host.toChars();
    // 将uri转换为char数组
    uri.toChars();
    internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
}

当请求全称为http://localhost:8080/demo/test/hello时,此时的host是localhost,uri是/demo/test/hello

Host和Context的映射

下面接着看internalMap

private final void internalMap(CharChunk host, CharChunk uri,
            String version, MappingData mappingData) throws IOException {
   

   if (mappingData.host != null) {
   
       // The legacy code (dating down at least to Tomcat 4.1) just
       // skipped all mapping work in this case. That behaviour has a risk
       // of returning an inconsistent result.
       // I do not see a valid use case for it.
       throw new AssertionError();
   }

   // Virtual host mapping
   // 获取当前配置的所有虚拟域名
   MappedHost[] hosts = this.hosts;
   // 找到当前请求的虚拟域名
   MappedHost mappedHost = exactFindIgnoreCase(hosts, host);
   if (mappedHost == null) {
   
       // Note: Internally, the Mapper does not use the leading * on a
       //       wildcard host. This is to allow this shortcut.
       int firstDot = host.indexOf('.');
       if (firstDot > -1) {
   
           int offset = host.getOffset();
           try {
   
               host.setOffset(firstDot + offset);
               mappedHost = exactFindIgnoreCase(hosts, host);
           } finally {
   
               // Make absolutely sure this gets reset
               host.setOffset(offset);
           }
       }
       if (mappedHost == null) {
   
           mappedHost = defaultHost;
           if (mappedHost == null) {
   
               return;
           }
       }
   }
   // 将mappedHost的object对象赋值给请求的mappingData的host对象,这里的mappedHost.object就是对应虚拟域名的StandardHost对象
   mappingData.host = mappedHost.object;

   if (uri.isNull()) {
   
       // Can't map context or wrapper without a uri
       return;
   }

   uri.setLimit(-1);

   // Context mapping
   // 进行应用的映射
   // ContextList对象保存了当前虚拟域名下面部署的所有应用名称
   // 以我当前运行的实例为例,一共运行如下几个应用:/demo /docs /host-manager /manager
   ContextList contextList = mappedHost.contextList;
   // 对应上面几个应用
   MappedContext[] contexts = contextList.contexts;
   // 找到当前请求的应用的下标
   int pos = find(contexts, uri);
   if (pos == -1) {
   
       return;
   }

   int lastSlash = -1;
   int uriEnd = uri.getEnd();
   int length = -1;
   boolean found = false;
   MappedContext context = null;
   while (pos >= 0) {
   
       context = contexts[pos];
       // 判断uri是否以应用的名称开头
       if (uri.startsWith(context.name)) {
   
           length = context.name.length();
           // uri和应用的名称完全匹配
           if (uri.getLength() == length) {
   
               found = true;
               break;
           } else if (uri.startsWithIgnoreCase("/", length)) {
   
           		// uri将前面和应用名称匹配的部分去除之外,第一个字符是/
               found = true;
               break;
           }
       }
       if (lastSlash == -1) {
   
           lastSlash = nthSlash(uri, contextList.nesting + 1);
       } else {
   
           lastSlash = lastSlash(uri);
       }
       uri.setEnd(lastSlash);
       pos = find(contexts, uri);
   }
   uri.setEnd(uriEnd);

   if (!found) {
   
       if (contexts[0].name.equals("")) {
   
           context = contexts[0];
       } else {
   
           context = null;
       }
   }
   if (context == null) {
   
       return;
   }

   // 设置请求对象的mappding的contextPath,设置为当前应用的名称
   mappingData.contextPath.setString(context.name);

   ContextVersion contextVersion = null;
   ContextVersion[] contextVersions = context.versions;
   final int versionCount = contextVersions.length;
   if (versionCount > 1) {
   
       Context[] contextObjects = new Context[contextVersions.length];
       for (int i = 0; i < contextObjects.length; i++) {
   
           contextObjects[i] = contextVersions[i].object;
       }
       mappingData.contexts = contextObjects;
       if (version != null) {
   
           contextVersion = exactFind(contextVersions, version);
       }
   }
   if (contextVersion == null) {
   
       // Return the latest version
       // The versions array is known to contain at least one element
       contextVersion = contextVersions[versionCount - 1];
   }
   // 将对应的StandardContext对象赋值给请求的mappingData
   mappingData.context = contextVersion.object;
   mappingData.contextSlashCount = contextVersion.slashCount;

   // Wrapper mapping
   if (!contextVersion.isPaused()) {
   
   		// 开始进行servlet的映射
       internalMapWrapper(contextVersion, uri, mappingData);
   }
}

Servlet的映射

private final void internalMapWrapper(ContextVersion contextVersion,
                                          CharChunk path,
                                          MappingData mappingData) throws IOException {
   

    int pathOffset = path.getOffset()
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值