TOMCAT 源码分析 -- 一次请求

简介: TOMCAT 源码分析 -- 一次请求

TOMCAT 源码分析 – 一次请求

前语

在上一篇源码分析《TOMCAT源码分析–启动》中已经知道,Tomcat在启动中,会通过NIO监听端口,而真正去接收请求的是pollerThread.start()轮询线程的启动,那么请求的入口应该是到NIO中,最后被轮询线程发现并被处理,那么自然就要去看Poller线程的run()方法,看其是如何处理。(PS:心中要有上一篇里面的模块架构图,很重要!)

端点接收请求

// org.apache.tomcat.util.net.NioEndpoint.Poller#run
      @Override
        public void run() {
            // Loop until destroy() is called
            // 一直循环,直到destroy方法被调用
            while (true) {
                boolean hasEvents = false;
                try {
                    if (!close) {
                        // 遍历事件队列判断是否有事件(请求)待处理
                        hasEvents = events();
                        if (wakeupCounter.getAndSet(-1) > 0) {
                            // If we are here, means we have other stuff to do
                            // Do a non blocking select
                            // 如果在这个分支,说明有其他工作要做,这里就不去做一个阻塞的select
                            keyCount = selector.selectNow();
                        } else {
                            keyCount = selector.select(selectorTimeout);
                        }
                        wakeupCounter.set(0);
                    }
                } catch (Throwable x) {
                }
                // ...
                while (iterator != null && iterator.hasNext()) {
                    SelectionKey sk = iterator.next();
                    NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
                    if (socketWrapper == null) {
                        iterator.remove();
                    } else {
                        // socketWrapper 包装不为空,将其取出,进行处理
                        iterator.remove();
                        processKey(sk, socketWrapper);
                    }
                }
                // Process timeouts
                timeout(keyCount,hasEvents);
            }
            getStopLatch().countDown();
        }

端点一直进行循环,检测有没有可用的NIO准备就绪,如果有就拿去processKey消费掉。那么看看它具体怎么做的

// org.apache.tomcat.util.net.NioEndpoint.Poller#processKey
protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {
            try {
                if (close) {
                    cancelledKey(sk, socketWrapper);
                } else if (sk.isValid() && socketWrapper != null) {
                    if (sk.isReadable() || sk.isWritable()) {
                        if (socketWrapper.getSendfileData() != null) {
                            processSendfile(sk, socketWrapper, false);
                        } else {
                            unreg(sk, socketWrapper, sk.readyOps());
                            boolean closeSocket = false;
                            // Read goes before write
                            if (sk.isReadable()) {
                                if (socketWrapper.readOperation != null) {
                                    if (!socketWrapper.readOperation.process()) {
                                        closeSocket = true;
                                    }
                                    // 最终进入下面这个分支
                                } else if (!processSocket(socketWrapper, SocketEvent.OPEN_READ, true)) {
                                    closeSocket = true;
                                }
                            }
                        }
                    }
                } else {
                }
            } catch (CancelledKeyException ckx) {
            } catch (Throwable t) {
            }
        }
// org.apache.tomcat.util.net.AbstractEndpoint#processSocket
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
            SocketEvent event, boolean dispatch) {
        try {
            if (socketWrapper == null) {
                return false;
            }
            SocketProcessorBase<S> sc = null;
            // 想从缓存处理器中取一个出来先
            if (processorCache != null) {
                sc = processorCache.pop();
            }
            // 如果没取到则创建一个处理器
            if (sc == null) {
                sc = createSocketProcessor(socketWrapper, event);
            } else {
                sc.reset(socketWrapper, event);
            }
            // 获取线程池处理 -- 这里也就是并发去处理这个处理器了,这个线程就自己回去处理NIO的监听了。
            Executor executor = getExecutor();
            if (dispatch && executor != null) {
                // 可以观察到进入了这个分支
                executor.execute(sc);
            } else {
                // 如果没有获取到线程池就直接调用这个处理器的run方法
                sc.run();
            }
        } catch (RejectedExecutionException ree) {
        }
      // ...
        return true;
    }
  • 从上面的执行过程可以看到他使用线程池提交了sc处理器,那么就去看处理器的run方法,且从运行中可以看到这个sc实际为NioEndPoint$SocketProcessor的实例,发现这个实例中并没有run方法,但是其超类SocketProcessorBase中的run方法实际调用了实例的doRun方法
// org.apache.tomcat.util.net.NioEndpoint.SocketProcessor#doRun
      @Override
        protected void doRun() {
            NioChannel socket = socketWrapper.getSocket();
            Poller poller = NioEndpoint.this.poller;
            try {
                int handshake = -1;
                if (handshake == 0) {
                    SocketState state = SocketState.OPEN;
                    // Process the request from this socket
                    // 很容器看出来,它将在下面获取处理器进行处理这个请求
                    if (event == null) {
                        state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
                    } else {
                        // 实际上,进的是这个分支,而event事件刚好也是上面的OPEN_READ
                        state = getHandler().process(socketWrapper, event);
                    }
            } catch (CancelledKeyException cx) {
            } catch (Throwable t) {
            } finally {
            }
        }
  • 从上面获取了处理器要进行处理,也就是上一篇中的coyote的端点EndPoint获取Processor,并且调用他的process方法进行处理。这个处理方法十分长,它的主要逻辑就是Handler获取真正的处理器Process,然后调用它的process方法处理
// org.apache.coyote.AbstractProtocol.ConnectionHandler#process
    @Override
        public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
            // 获取处理器 -- 实际本次请求当前处理器为Null
            Processor processor = (Processor) wrapper.getCurrentProcessor();
            try {
                if (processor == null) {
                    // 获取协议,需要通过协议去找对应的协议处理器 -- 这儿实际返回Null
                    String negotiatedProtocol = wrapper.getNegotiatedProtocol();
                    // ...
                }
                if (processor == null) {
                    // 还是null,就先去已经回收但还未释放的处理器栈中弹出一个来
                    // 因为已经使用过,所以这里就返回了Http11Processor
                    processor = recycledProcessors.pop();
                }
                if (processor == null) {
                    // 如果还没获取到处理器,就根据协议去创建一个对应的处理器,第一次访问时会如此
                    processor = getProtocol().createProcessor();
                    register(processor);
                }    
                do {
                    // 协议处理器开始处理包装的请求
                    state = processor.process(wrapper, status);
                } while ( state == SocketState.UPGRADING);
        }
  • 可以发现找到处理器后,调用process处理,不过这个方法是超类中的方法,且Http11Processor未进行覆盖
// org.apache.coyote.AbstractProcessorLight#process
@Override
    public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)
            throws IOException {
        SocketState state = SocketState.CLOSED;
        Iterator<DispatchType> dispatches = null;
        do {
            if (dispatches != null) {
            } else if (status == SocketEvent.DISCONNECT) {
                // Do nothing here, just wait for it to get recycled
            } else if (isAsync() || isUpgrade() || state == SocketState.ASYNC_END) {
                state = dispatch(status);
                state = checkForPipelinedData(state, socketWrapper);
            } else if (status == SocketEvent.OPEN_WRITE) {
                state = SocketState.LONG;
            } else if (status == SocketEvent.OPEN_READ) {
                // 根据DEBUG之前提到了,event的类型就是OPEN_READ,故进这个分支,进入核心的处理。
                state = service(socketWrapper);
            } else if (status == SocketEvent.CONNECT_FAIL) {
                logAccess(socketWrapper);
            } else {
                state = SocketState.CLOSED;
            }
        return state;
    }

那么state = service(socketWrapper);就是Http11Processor的核心处理了

// org.apache.coyote.http11.Http11Processor#service
@Override
    public SocketState service(SocketWrapperBase<?> socketWrapper)
        throws IOException {
        // ...
            // Process the request in the adapter
            if (getErrorState().isIoAllowed()) {
                try {
                    rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
                    // 就是这一步去找适配器并进行处理
                    // 获得CoyoteAdapter的实例
                    getAdapter().service(request, response);
                    if(keepAlive && !getErrorState().isError() && !isAsync() &&
                            statusDropsConnection(response.getStatus())) {
                        setErrorState(ErrorState.CLOSE_CLEAN, null);
                    }
                }   catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    log.error(sm.getString("http11processor.request.process"), t);
                    // 500 - Internal Server Error
                    response.setStatus(500);
                    setErrorState(ErrorState.CLOSE_CLEAN, t);
                    getAdapter().log(request, response, 0);
                }
            }
    }
  • 这一步很关键,他去获得了Adapter,也就是模块图中,要通过这个适配器,去进行将Request进行转换封装,最后调用Catalina进行处理的位置
// org.apache.catalina.connector.CoyoteAdapter#service
@Override
    public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
            throws Exception {
        // 将Request 进行转换封装
        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);
        req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());
        try {
            // Parse and set Catalina and configuration specific
            // 解析并设置Catalina和特定于配置 -- 什么意思呢,就是在这里去找配置-映射了
            // request parameters
            postParseSuccess = postParseRequest(req, request, res, response);
            if (postParseSuccess) {
                // 调用容器的管道,进行流水线处理了
                connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);
            }
        }
    }
  • 看到这儿就清晰多了,他两步走,第一步根据配置寻找映射,
  • 第二步,调用了Service容器的关掉进行处理这个请求,那么根据套娃模式,他也必将通过一层又一层的相似调用进去,拭目以待~
// org.apache.catalina.connector.CoyoteAdapter#postParseRequest
protected boolean postParseRequest(org.apache.coyote.Request req, Request request,
            org.apache.coyote.Response res, Response response) throws IOException, ServletException {
    // 获得请求解码后的地址
    MessageBytes undecodedURI = req.requestURI();
    while (mapRequired) {
            // This will map the the latest version by default
          // 在容器中寻找映射
            connector.getService().getMapper().map(serverName, decodedURI,
                    version, request.getMappingData());
    }
}
// map方法最终进入这个超类
// org.apache.catalina.mapper.Mapper#map(org.apache.tomcat.util.buf.MessageBytes, org.apache.tomcat.util.buf.MessageBytes, java.lang.String, org.apache.catalina.mapper.MappingData)
public void map(MessageBytes host, MessageBytes uri, String version,
                    MappingData mappingData) throws IOException {
        // 将主机名和URI做映射
        // 内部映射 
        internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
    }
// org.apache.catalina.mapper.Mapper#internalMap
private final void internalMap(CharChunk host, CharChunk uri,
            String version, MappingData mappingData) throws IOException {
        // Context mappinp
      // 在这一步执行完之后,在contextList中就已经可以看到配置的具体映射了
        ContextList contextList = mappedHost.contextList;
        MappedContext[] contexts = contextList.contexts;
        // Wrapper mapping
        if (!contextVersion.isPaused()) {
            // 将映射数据包装
            internalMapWrapper(contextVersion, uri, mappingData);
        }
    }
// org.apache.catalina.mapper.Mapper#internalMapWrapper
private final void internalMapWrapper(ContextVersion contextVersion,
                                          CharChunk path,
                                          MappingData mappingData) throws IOException {
    // ...
  // 这儿的逻辑很长,但一直调式,发现他最后走了Rule 7
        // Rule 7 -- Default servlet
        // 默认的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;
            }
            // Redirection to a folder
            char[] buf = path.getBuffer();
            if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
                String pathStr = path.toString();
                // Note: Check redirect first to save unnecessary getResource()
                //       call. See BZ 62968.
                if (contextVersion.object.getMapperDirectoryRedirectEnabled()) {
                    WebResource file;
                    // Handle context root
                    if (pathStr.length() == 0) {
                        file = contextVersion.resources.getResource("/");
                    } else {
                        file = contextVersion.resources.getResource(pathStr);
                    }
                    if (file != null && file.isDirectory()) {
                        // Note: this mutates the path: do not do any processing
                        // after this (since we set the redirectPath, there
                        // shouldn't be any)
                        path.setOffset(pathOffset);
                        path.append('/');
                        mappingData.redirectPath.setChars
                            (path.getBuffer(), path.getStart(), path.getLength());
                    } else {
                        mappingData.requestPath.setString(pathStr);
                        mappingData.wrapperPath.setString(pathStr);
                    }
                } else {
                    mappingData.requestPath.setString(pathStr);
                    mappingData.wrapperPath.setString(pathStr);
                }
            }
        }
}

把映射对象包装好后,进入第二步,去套娃中挖掘具体的处理:

// Calling the container
                connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);
// org.apache.catalina.core.StandardEngineValve#invoke
 @Override
    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. This is handled by the CoyoteAdapter.
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }
        // Ask this Host to process this request
        // 要求主机处理此请求
        host.getPipeline().getFirst().invoke(request, response);
    }
  • 可以看到进入了StandardEngineValve类中,那么根据上一篇的模块架构图,以及server.xml的配置就可以知道,它将一层一层处理管道线,一层一层的进行invoke。那么中间省略他套娃式的几步invoke,走到了下面StandardContextValve的代码
// org.apache.catalina.core.StandardContextValve#invoke
    @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
    // ...
        // 管道处理
        // 此时他已经一层一层的走进来,一直走到包装了servlet的wrapper
        wrapper.getPipeline().getFirst().invoke(request, response);
    }

那么看看这个wrapper的值是什么:StandardEngine[Catalina].StandardHost[localhost].StandardContext[/webmvc].StandardWrapper[springmvc],可以发现引擎是“Catalina”,虚拟主机是“localhost”,上下文是“webmvc”,进行处理的servlet是“springmvc”,那么就没错了。放入tomcat的webapps目录中的样例工程,就是这个上下文,以及spring mvc的servlet入口。

接下来,有了对应的wrapper之后,它还经过了过滤链进行一定的过滤filter,最终在下面这个地方进行调用:

// org.apache.catalina.core.ApplicationFilterChain#internalDoFilter
private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {
                // 真正调用servlet对请求做处理的位置,如果是spring mvc 这里就是dispatcherServlet对象
                servlet.service(request, response);
        }
  • DEBUG可以看到这个servlet实例的值是DispatcherServlet,同时也可以看到它的上下文对象是spring的上下文,如下图:

那么,接下来请求的处理就是调用spring mvc进行完成了,tomcat对请求的处理分发就完成了!

结语

Tomcat的一次请求走的代码看起来很长,实际上了解其架构之后,逻辑还是十分清晰的。

本次在Tomcat源码的入门学习中,有以下体会:

  1. Tomcat的架构清晰,多层嵌套,看起来复杂,实际上就是剥洋葱的行为,看源码的过程根据DEBUG来追踪就可以清晰的一层一层的进去。
  2. 看源码DEBUG真的很重要!
  3. 了解架构,从宏观出发,不抓细枝末节,是快速看完源码,了解主要思想的重要途径。过分追求每一行代码的行为容易忘了自己前面看过的,以及你到底想看的是什么。
  4. 了解完请求过程,那么对一次请求最终调用之前打一个Log日志,做统计等等都是可以完成的了~

推广

推广我一分钱也没得赚!!!!

喝水不忘打井人吧,这个是在拉勾教育上看的《Tomcat核心源码剖析》,应癫出品。只有三天的课程,第三天还会推荐你报班(手动滑稽~)。

槽点: 这是个“训练营”课程,18元人民币,有效期18天。到期就没得看了,也督促我三天看完,五天内完成笔记~

目录
相关文章
|
4月前
|
JSON 前端开发 Java
图解HTTP请求Tomcat服务器实现前后端交互-2
图解HTTP请求Tomcat服务器实现前后端交互
56 0
|
4月前
|
前端开发 JavaScript Java
图解HTTP请求Tomcat服务器实现前后端交互-1
图解HTTP请求Tomcat服务器实现前后端交互
101 0
|
4月前
|
XML Java 应用服务中间件
SpringBoot配置外部Tomcat项目启动流程源码分析(长文)
SpringBoot配置外部Tomcat项目启动流程源码分析(长文)
52 0
|
9月前
|
存储 缓存 前端开发
07.Tomcat源码分析——类加载体系
由于在生产环境中,Tomcat一般部署在Linux系统下,所以本文将以 startup.sh shell脚本为准,对Tomcat的启动进行分析。
31 0
07.Tomcat源码分析——类加载体系
|
10月前
|
Java 应用服务中间件
TOMCAT 源码分析 -- 构建环境
TOMCAT 源码分析 -- 构建环境
77 0
|
10月前
|
监控 前端开发 Java
TOMCAT 源码分析 -- 启动(下)
TOMCAT 源码分析 -- 启动
71 0
|
10月前
|
XML 前端开发 Java
TOMCAT 源码分析 -- 启动(上)
TOMCAT 源码分析 -- 启动
107 0
|
10月前
|
Java 应用服务中间件 容器
Tomcat源码分析之中文乱码(一)
Tomcat源码分析之中文乱码(一)
137 0
|
10月前
|
算法 安全 应用服务中间件
Tomcat源码分析之 doGet方法(四)
Tomcat源码分析之 doGet方法(四)
33 0
|
10月前
|
设计模式 应用服务中间件 容器
Tomcat源码分析之 doGet方法(三)
Tomcat源码分析之 doGet方法(三)
42 0