Tomcat8.5.43源码分析-(5)Tomcat进行Web请求处理 第一部分

Tomcat作为Servlet容器,其最重要的应用仍然是处理web请求。

这里就沿着Web请求处理这条线,继续探究Tomcat。

当Connector接收到请求后,会调用CoyoteAdapter.service(),这里选取重要的部分:

@Override
    public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
            throws Exception {

        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);
        ...
        try {
            // Parse and set Catalina and configuration specific
            // request parameters
            postParseSuccess = postParseRequest(req, request, res, response);
           ...
            }
        }
    }

根据请求org.apache.coyote.Request,创建了org.apache.catalina.connector.Request;

根据响应org.apache.coyote.Response,创建了org.apache.catalina.connector.Response。

然后是postParseRequest方法。该方法是对请求解析的重要方法,非常冗长,我们选取重要的部分分析。

首先会对请求的URL进行合法性校验,若请求不合法,直接返回400错误,这里可以说的不多,大多数的工作都在做字符的匹配。

然后定义了三个重要的局部变量:

        // Version for the second mapping loop and
        // Context that we expect to get for that version
        String version = null;
        Context versionContext = null;
        boolean mapRequired = true;
  • version:需要匹配的二次循环的版本号,初始化为null
  • versionContext:用于暂存按照会话ID匹配的Context,初始化为null
  • mapRequired:是否需要映射,控制循环,初始化为true

之后以while(mapRequired)开启循环。

            // This will map the the latest version by default
            connector.getService().getMapper().map(serverName, decodedURI,
                    version, request.getMappingData());

这里调用Mapper.map()方法对URI进行解析。具体的解析过程在后面分析。

初次调用时,由于version为空,这里会匹配到所有的版本,并在MappingData.contexts中存放所有结果,并在MappingData.context里存放最新版本。

如果没有匹配到任何结果,返回404错误:

            // If there is no context at this point, either this is a 404
            // because no ROOT context has been deployed or the URI was invalid
            // so no context could be mapped.
            if (request.getContext() == null) {
                // Don't overwrite an existing error
                if (!response.isError()) {
                    response.sendError(404, "Not found");
                }
                // Allow processing to continue.
                // If present, the error reporting valve will provide a response
                // body.
                return true;
            }

如果有匹配到结果,则继续。尝试从URL、Cookie、SSL会话中获取会话ID,即sessionID。接着使用sessionID寻找匹配的最新版本。

                Context[] contexts = request.getMappingData().contexts;
                // Single contextVersion means no need to remap
                // No session ID means no possibility of remap
                if (contexts != null && sessionID != null) {
                    // Find the context associated with the session
                    for (int i = contexts.length; i > 0; i--) {
                        Context ctxt = contexts[i - 1];//递减,查找最新版本
                        if (ctxt.getManager().findSession(sessionID) != null) {
                            // We found a context. Is it the one that has
                            // already been mapped?
                            if (!ctxt.equals(request.getMappingData().context)) {
                                // Set version so second time through mapping
                                // the correct context is found
                                version = ctxt.getWebappVersion();
                                versionContext = ctxt;
                                // Reset mapping
                                request.getMappingData().recycle();
                                mapRequired = true;
                                // Recycle cookies and session info in case the
                                // correct context is configured with different
                                // settings
                                request.recycleSessionInfo();
                                request.recycleCookieInfo(true);
                            }
                            break;
                        }
                    }
                }

初次匹配时version和versionContext为空。

若sessionID为空,则MappingData.context即为匹配结果。

若sessionID不为空,则从MappingData.contexts中查找包含sessionID的最新版本。

  1. 从MappingData.contexts找到sessionID,且与从URL、Cookie、SSL会话中获取会话ID相同,则使用此结果;
  2. 从MappingData.contexts找到sessionID,且与从URL、Cookie、SSL会话中获取会话ID不相同,则将version设置为查询版本,versionContext设置为查询结果,再次开启循环。

Tomcat会确保找到的Context符合以下要求:

  • 匹配请求路径
  • 如果有有效会话,包含有效会话的最新版本
  • 如果没有有效会话,则为所有匹配请求的最新版本
  • Context必须有效(非暂停)

 继续探讨Mapper.map()方法对URI进行解析的过程。该方法会调用Mapper.internalMap()方法作为解析主方法。

Mapper类对Host、Context、Wrapper进行了封装,匹配的返回结果放置于MappingData中。

解析开始,首先是查找Host。

这里我以http://localhost:8080/hello,http://127.0.0.1:8080/hello 为例分保进行解析,发现:

  • 以localhost为请求域名的,会直接找到localhost的对应host;
  • 127.0.0.1为请求地址的,会找不到对应的host,并使用默认的defaultHost。

找到Host之后,更新mappingData:

mappingData.host = mappedHost.object;

接着从host中取出Context集合:

        // Context mapping
        ContextList contextList = mappedHost.contextList;
        MappedContext[] contexts = contextList.contexts;

要注意的是,取出的Context集合contexts是有序的。资料上显示是按照名称的ASCII正序排列。然后查找uri所对应的context的位置:

        int pos = find(contexts, uri);
find(map, name, name.getStart(), name.getEnd());

Mapper.find()方法如下:

   /**
     * Find a map element given its name in a sorted array of map elements.
     * This will return the index for the closest inferior or equal item in the
     * given array.
     */
    private static final <T> int find(MapElement<T>[] map, CharChunk name,
                                  int start, int end) {

        int a = 0;
        int b = map.length - 1;

        // Special cases: -1 and 0
        if (b == -1) {
            return -1;
        }

        if (compare(name, start, end, map[0].name) < 0 ) {
            return -1;
        }
        if (b == 0) {
            return 0;
        }

        int i = 0;
        while (true) {
            i = (b + a) >>> 1;
            int result = compare(name, start, end, map[i].name);
            if (result == 1) {
                a = i;
            } else if (result == 0) {
                return i;
            } else {
                b = i;
            }
            if ((b - a) == 1) {
                int result2 = compare(name, start, end, map[b].name);
                if (result2 < 0) {
                    return a;
                } else {
                    return b;
                }
            }
        }

    }

这里实现了一个简单的二分查找,试图找到一个精准的匹配结果。若没有最精准的结果,则返回比url小的最大值的位置。返回的位置存入pos这个int变量中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值