Tomcat 源码分析(二)-请求分析(3)

38 篇文章 0 订阅
27 篇文章 0 订阅

Tomcat 源码分析(二)-请求分析(3)

三、请求与容器中具体组件的匹配

到,前一篇为止,已经分析到了org.apache.coyote.http11.AbstractHttp11Processor类 process 方法 ,了解到了请求从Socket的IO流中取出字节流,很久http协议将字节流组装到Tomcat的内部对象org.apache.coyote.Request中的相关属性中。

接下来这里,要分析了解的,就是:Tomcat的内部请求对象怎么从 Connector 到 Engine 到 Host 到 Context 最后到 Servlet 的过程的。

开始进行内部传递处理的地方

adapter.service(request, response);

这一行代码就是在AbstractHttp11Processor的process 方法中,使用适配器处理的地方。【是在处理socket的数据,解析请求头之后那里】

这里的adapter对象,是在Http11Processor 创建的时候设置的:

//org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler类的 createProcessor 方法
 protected Http11Protocol proto;
 protected Http11Processor createProcessor() {
            Http11Processor processor = new Http11Processor(
                    proto.getMaxHttpHeaderSize(), proto.getRejectIllegalHeaderName(),
                    (JIoEndpoint)proto.endpoint, proto.getMaxTrailerSize(),
                    proto.getAllowedTrailerHeadersAsSet(), proto.getMaxExtensionSize(),
                    proto.getMaxSwallowSize(), proto.getRelaxedPathChars(),
                    proto.getRelaxedQueryChars());
            processor.setAdapter(proto.adapter); //可以看到在这里进行的设置
.....
            register(processor);
            return processor;
        }

这里可以看的出来,adapter 使用的是Http11Protocol的adapter 变量,

变量的创建则是在Connector类的初始化方法initInternal 中:(配置文件Connector节点的初始化过程中)

//org.apache.catalina.connector;
@Override
protected void initInternal() throws LifecycleException {
    super.initInternal();
    // Initialize adapter 在这里对adapter进行初始化,使用的是CoyoteAdapter类
    adapter = new CoyoteAdapter(this);
    protocolHandler.setAdapter(adapter);
	//上面这种双向绑定对象的方式在Tomcat中相当的常见啊 _(¦3」∠)_
    // Make sure parseBodyMethodsSet has a default
    if( null == parseBodyMethodsSet ) {
        setParseBodyMethods(getParseBodyMethods());
    }
  ......
    try {
        protocolHandler.init();
    } catch (Exception e) {
    .......
    }
    // Initialize mapper listener
    mapperListener.init();
}

实际执行的处理方法

这里可知道了adapter.service(request, response)所调用的方法实际执行的是org.apache.catalina.connector.CoyoteAdapter类的 service 方法。

部分关键的源码分析:

public void service(org.apache.coyote.Request req,
                        org.apache.coyote.Response res)
        throws Exception {
		//	这里的时候,就已经把参数req 从org.apache.coyote.Request 转换为 org.apache.catalina.connector.Request,这个才是真正在Tomcat容器流转的对象,也是我们平常使用的。
    // 这里的getNote(1)基本就是req差不多的东东,但是少了一些 headers,cookies之类的信息
        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);

        if (request == null) {
			//这里开始 就是给创建的Request 和 Response 对象设置各种的值和传递的参数
            // Create objects
            request = connector.createRequest();
            request.setCoyoteRequest(req);
            response = connector.createResponse();
            response.setCoyoteResponse(res);
            // Link objects
            request.setResponse(response);
            response.setRequest(request);
            // Set as notes
            req.setNote(ADAPTER_NOTES, request);
            res.setNote(ADAPTER_NOTES, response);
            // Set query string encoding
            req.getParameters().setQueryStringEncoding
                (connector.getURIEncoding());
        }
        if (connector.getXpoweredBy()) {
            response.addHeader("X-Powered-By", POWERED_BY);
        }
        boolean comet = false;
        boolean async = false;
        boolean postParseSuccess = false;
        try {
            // Parse and set Catalina and configuration specific
            // request parameters 分析并设置catalina和特定于配置的请求参数
            // 这里是一个重点,postParseRequest方法会吧req对象里面的一些变量 host、context、warp 信息
            // 设置到新的request,response中去,已达到在其中流转。
            req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
            postParseSuccess = postParseRequest(req, request, res, response);
            //*****************↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
            if (postParseSuccess) {
                //check valves if we support async  
request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
                // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 这里是一个很重要的地方,进行了数据的传递流转
                connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
                 //这个地方在下面说明的 阀机制原理的时候会分析到这里↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
                if (request.isComet()) {
                    if (!response.isClosed() && !response.isError()) {
                        if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
                            // Invoke a read event right away if there are available bytes
                            if (event(req, res, SocketStatus.OPEN_READ)) {
                                comet = true;
                                res.action(ActionCode.COMET_BEGIN, null);
                            } else {
                                return;
                            }
                        } else {
                            comet = true;
                            res.action(ActionCode.COMET_BEGIN, null);
                        }
                    } else {
                        // Clear the filter chain, as otherwise it will not be reset elsewhere
                        // since this is a Comet request
                        request.setFilterChain(null);
                    }
                }
            }

service 方法的主要功能就是,创建了最终我们使用的Request和Response,并对其请求传递来的各种数据进行设置到新的对象里面去,包括host、context、warp 信息(这些请求接下来要在Tomcat中流转的信息)。

这个是在第37行的postParseRequest(req, request, res, response)方法中实现的。

分析一下对其参数的匹配过程

具体的,就是来看一下这个postParseRequest方法:

/** Parse additional request parameters.分析其他请求参数,设置了各种参数到新的对象里面去*/
    protected boolean postParseRequest(org.apache.coyote.Request req,
                                       Request request,
                                       org.apache.coyote.Response res,
                                       Response response)
            throws Exception {

        if (! req.scheme().isNull()) {
            request.setSecure(req.scheme().equals("https"));
        } else {
            //使用连接器方案和安全配置(defaults to "http" and false respectively)
            req.scheme().setString(connector.getScheme());
            request.setSecure(connector.getSecure());
        }

        // 配置端口 未显式设置。根据方案使用默认端口 代理端口 没配置这段代码就跳过了
        String proxyName = connector.getProxyName();
        int proxyPort = connector.getProxyPort();
        if (proxyPort != 0) {
            req.setServerPort(proxyPort);
        } else if (req.getServerPort() == -1) {
            if (req.scheme().equals("https")) {
                req.setServerPort(443);
            } else {
                req.setServerPort(80);
            }
        }
        if (proxyName != null) {
            req.serverName().setString(proxyName);
        }

        // Copy the raw URI to the decodedURI
        MessageBytes decodedURI = req.decodedURI();
        decodedURI.duplicate(req.requestURI());
        parsePathParameters(req, request);

        // 这里看着是做一些URI的校验
        try {
            req.getURLDecoder().convert(decodedURI, false);
        } catch (IOException ioe) {
            res.setStatus(400);
            res.setMessage("Invalid URI: " + ioe.getMessage());
            connector.getService().getContainer().logAccess(
                    request, response, 0, true);
            return false;
        }
        if (!normalize(req.decodedURI())) {
            res.setStatus(400);
            res.setMessage("Invalid URI");
            connector.getService().getContainer().logAccess(
                    request, response, 0, true);
            return false;
        }
        convertURI(decodedURI, request);
        if (!checkNormalize(req.decodedURI())) {
            res.setStatus(400);
            res.setMessage("Invalid URI character encoding");
            connector.getService().getContainer().logAccess(
                    request, response, 0, true);
            return false;
        }

        // Request mapping.
        MessageBytes serverName;
        if (connector.getUseIPVHosts()) {
            serverName = req.localName();
            if (serverName.isNull()) {
                res.action(ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, null);
            }
        } else {
            serverName = req.serverName();
        }
        if (request.isAsyncStarted()) {
            request.getMappingData().recycle();
        }

        String version = null;
        Context versionContext = null;
        boolean mapRequired = true;

        while (mapRequired) {
            // wrapper,contxt,(request原本是空的) - 这里会做映射
            // 这里的map方法会对request.getMappingData()->MappingData 按请求的信息设置成员变量 host、context、warp 的信息,然后再把本身的 MappingData 的值对应设置到request;
            // 这里处理的 host、context、warp 的信息就确定了请求之后的处理方向了
            connector.getMapper().map(serverName, decodedURI, version,
                                      request.getMappingData());
            request.setContext((Context) request.getMappingData().context);
            request.setWrapper((Wrapper) request.getMappingData().wrapper);

// If there is no context at this point, it is likely no ROOT context has been deployed
            // 做一些请求数据的校验
            if (request.getContext() == null) {
                res.setStatus(404);
                res.setMessage("Not found");
                // No context, so use host
                Host host = request.getHost();
                // Make sure there is a host (might not be during shutdown)
                if (host != null) {
                    host.logAccess(request, response, 0, true);
                }
                return false;
            }

            // sessionID的解析
            String sessionID;
            if (request.getServletContext().getEffectiveSessionTrackingModes()
                    .contains(SessionTrackingMode.URL)) {

                // Get the session ID if there was one
                sessionID = request.getPathParameter(
                        SessionConfig.getSessionUriParamName(
                                request.getContext()));
                if (sessionID != null) {
                    request.setRequestedSessionId(sessionID);
                    request.setRequestedSessionURL(true);
                }
            }

            // Look for session ID in cookies and SSL session
            parseSessionCookiesId(req, request);
            parseSessionSslId(request);

            sessionID = request.getRequestedSessionId();
            
。。还有一堆代码 。。就不看了。。反正我是看不懂。。最多看个大概意思。。设置参数,校验下参数之类的。。

这里对上面的代码稍微说明一下:

参数匹配,在从原来的req复制过来之后,对很多的参数进行了校验判断。

其中,Host、Context、Wrapper 参数的设置尤其的重(在85-88行),这里的map方法在设置MappingData中的Host、Context、Wrapper信息的时候,是做了匹配的,这些信息表示的就是本次的请求之后要处理的对象了。

在Tomcat 中,Host、Context、Wrapper为一对多的关系,在请求进来的时候,把他们用Map维护到一个Connector 中,确保了一个请求,就指定一个web应用进行处理了。

这里的Map是怎么进行匹配的,我也不懂了╮(╯_╰)╭ 我看着应该是根据URI地址进行匹配的

匹配完成了

到这里,容器使用的Request,Response 对象已经重新创建好了,并且已经对一些参数进行设置和复制。

同时,是用Map的方法,对Context,Wrapper 参数的值做了对应的匹配,设置。

之后的处理流转就按照配置的参数进行了。

参考资料


小杭 2019-03-15 [(--)]zzz

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小_杭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值