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天。到期就没得看了,也督促我三天看完,五天内完成笔记~

目录
相关文章
|
6月前
|
应用服务中间件
从零手写实现 tomcat-03-请求和响应的抽象
该文档介绍了 MiniCat 项目,它是一个简单的 HTTP 服务器实现。v1 版本中, MiniCatRequest 对象解析 HTTP 请求,包括方法、URL 和输入流,而 MiniCatResponse 使用输出流处理响应。start 方法使用这些封装后的对象处理网络通信。在 v2 版本,服务器添加了返回静态资源文件的功能,如 HTML,通过解析 URL 并读取对应本地文件内容来响应请求。测试示例展示了如何访问和显示 index.html。
|
9天前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
31 1
|
2月前
|
监控 网络协议 应用服务中间件
【Tomcat源码分析】从零开始理解 HTTP 请求处理 (第一篇)
本文详细解析了Tomcat架构中复杂的`Connector`组件。作为客户端与服务器间沟通的桥梁,`Connector`负责接收请求、封装为`Request`和`Response`对象,并传递给`Container`处理。文章通过四个关键问题逐步剖析了`Connector`的工作原理,并深入探讨了其构造方法、`init()`与`start()`方法。通过分析`ProtocolHandler`、`Endpoint`等核心组件,揭示了`Connector`初始化及启动的全过程。本文适合希望深入了解Tomcat内部机制的读者。欢迎关注并点赞,持续更新中。如有问题,可搜索【码上遇见你】交流。
【Tomcat源码分析】从零开始理解 HTTP 请求处理 (第一篇)
|
2月前
|
人工智能 前端开发 Java
【Tomcat源码分析】启动过程深度解析 (二)
本文深入探讨了Tomcat启动Web应用的过程,重点解析了其加载ServletContextListener及Servlet的机制。文章从Bootstrap反射调用Catalina的start方法开始,逐步介绍了StandardServer、StandardService、StandardEngine、StandardHost、StandardContext和StandardWrapper的启动流程。每个组件通过Lifecycle接口协调启动,子容器逐层启动,直至整个服务器完全启动。此外,还详细分析了Pipeline及其Valve组件的作用,展示了Tomcat内部组件间的协作机制。
【Tomcat源码分析】启动过程深度解析 (二)
|
2月前
|
前端开发 Java 应用服务中间件
【Tomcat源码分析 】"深入探索:Tomcat 类加载机制揭秘"
本文详细介绍了Java类加载机制及其在Tomcat中的应用。首先回顾了Java默认的类加载器,包括启动类加载器、扩展类加载器和应用程序类加载器,并解释了双亲委派模型的工作原理及其重要性。接着,文章分析了Tomcat为何不能使用默认类加载机制,因为它需要解决多个应用程序共存时的类库版本冲突、资源共享、类库隔离及JSP文件热更新等问题。最后,详细展示了Tomcat独特的类加载器设计,包括Common、Catalina、Shared、WebApp和Jsp类加载器,确保了系统的稳定性和安全性。通过这种设计,Tomcat实现了不同应用程序间的类库隔离与共享,同时支持JSP文件的热插拔。
【Tomcat源码分析 】"深入探索:Tomcat 类加载机制揭秘"
|
2月前
|
设计模式 应用服务中间件 容器
【Tomcat源码分析】Pipeline 与 Valve 的秘密花园
本文深入剖析了Tomcat中的Pipeline和Valve组件。Valve作为请求处理链中的核心组件,通过接口定义了关键方法;ValveBase为其基类,提供了通用实现。Pipeline则作为Valve容器,通过首尾相连的Valve链完成业务处理。StandardPipeline实现了Pipeline接口,提供了详细的Valve管理逻辑。通过对代码的详细分析,揭示了模板方法模式和责任链模式的应用,展示了系统的扩展性和模块化设计。
【Tomcat源码分析】Pipeline 与 Valve 的秘密花园
|
2月前
|
设计模式 人工智能 安全
【Tomcat源码分析】生命周期机制 Lifecycle
Tomcat内部通过各种组件协同工作,构建了一个复杂的Web服务器架构。其中,`Lifecycle`机制作为核心,管理组件从创建到销毁的整个生命周期。本文详细解析了Lifecycle的工作原理及其方法,如初始化、启动、停止和销毁等关键步骤,并展示了LifecycleBase类如何通过状态机和模板模式实现这一过程。通过深入理解Lifecycle,我们可以更好地掌握组件生命周期管理,提升系统设计能力。欢迎关注【码上遇见你】获取更多信息,或搜索【AI贝塔】体验免费的Chat GPT。希望本章内容对你有所帮助。
|
3月前
|
网络协议 Java 应用服务中间件
Tomcat源码分析 (一)----- 手撕Java Web服务器需要准备哪些工作
本文探讨了后端开发中Web服务器的重要性,特别是Tomcat框架的地位与作用。通过解析Tomcat的内部机制,文章引导读者理解其复杂性,并提出了一种实践方式——手工构建简易Web服务器,以此加深对Web服务器运作原理的认识。文章还详细介绍了HTTP协议的工作流程,包括请求与响应的具体格式,并通过Socket编程在Java中的应用实例,展示了客户端与服务器间的数据交换过程。最后,通过一个简单的Java Web服务器实现案例,说明了如何处理HTTP请求及响应,强调虽然构建基本的Web服务器相对直接,但诸如Tomcat这样的成熟框架提供了更为丰富和必要的功能。
|
5月前
|
缓存 负载均衡 NoSQL
Redis系列学习文章分享---第十四篇(Redis多级缓存--封装Http请求+向tomcat发送http请求+根据商品id对tomcat集群负载均衡)
Redis系列学习文章分享---第十四篇(Redis多级缓存--封装Http请求+向tomcat发送http请求+根据商品id对tomcat集群负载均衡)
82 1