掘金 后端 ( ) • 2024-04-25 09:37

当一个请求(HTTP)到达服务器,Tomcat会将它做一系列处理,包装成HttpServletRequest提供易用的API供我们使用。在处理完成用户的业务逻辑,又会返回HttpServletResponse,对请求进行应答。那么整个过程都经过了哪些步骤呢?通过下面这张图基本就可以看出来一个请求的基本处理过程。

请求处理过程

如果有看过Tomcat核心组件的同学应该知道Tomcat是通过Connector来接收处理外部请求的。从上图也可以看出,一个请求是通过Connector下的EndPoint组件接入(Acceptor是EndPoint下的不同协议线程实现类),然后经过多重处理最终到达Servlet。那我们首先看下服务是如何暴露的吧,也自然就知道请求的入口。

EndPoint服务暴露

以我最常用的Nio2Endpoint(也是最复杂的)为例,服务暴露过程是在初始化过程,以Connector的init方法为入口可以看到如下调用过程。Connector最终会调用NioEndPoint的bind方法,bind方法中会开启一个ServerSocketChannel,并且绑定到本地的端口上。通过这个暴露端口就可以接收到外部的请求了。

EndPoint服务暴露

EndPoint启动

接着我们看下EndPoint对请求的处理,在Connector的start过程中会调用NioEndpoint的start,这个过程其实主要创建并启动了两种线程:1、Acceptor线程负责请求的接收做一定的转换;2、Poller线程负责请求的处理。

EndPoint启动

Connector处理过程

EndPoint请求接收

protected class Acceptor extends AbstractEndpoint.Acceptor {
        @Override
        public void run() {
            // Loop until we receive a shutdown command
            while (running) {
                try {
                	... ...
                    SocketChannel socket = null;
                    try {
                        // Accept the next incoming connection from the server
                        // socket
                        socket = serverSock.accept();
                    }
                    ... ...
                    // Configure the socket
                    if (running && !paused) {
                        // setSocketOptions() will hand the socket off to
                        // an appropriate processor if successful
                        if (!setSocketOptions(socket)) {
                            closeSocket(socket);
                        }
                    } else {
                        closeSocket(socket);
                    }
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    log.error(sm.getString("endpoint.accept.fail"), t);
                }
            }
            state = AcceptorState.ENDED;
        }
    }

我们看下Acceptor线程的核心代码,主要就是监听在上文中创建的serverSock处,不断监听是否有请求到达,注意这里的serverSock是blocking的。如果接收到连接,则会调用NioEndPoint的setSocketOptions方法。对请求做读取、解析、包装、处理等一系列操作,整体流程如下。

Acceptor处理

在setSocketOptions中,会将SocketChannel对象包装进NioChannel中,并进一步包装成NioSocketWrapper并注册到Poller中。Poller将创建一个PollerEvent,放到Poller对象持有的队列中。

而Poller本身就是一个线程,会一直循环执行如下操作

  1. 执行events方法,events其实就是从Poller队列中取PollerEvent,并执行run方法,在PollerEvent的run方法中会对PollerEvent中持有的SocketChannel注册选择器(selector)及感兴趣的操作,这也是NIO的经典用法
  2. 从selector中获取IO操作已经准备完毕的通道数
  3. 当存在已经准备好IO操作的通道时候,通过selector获取到准备好的通道Key迭代器依次处理(processKey方法)每个key
  4. processKey方法将调用NioEndPoint的processSocket方法,这里会创建SocketProcessor对象,然后提交到线程池执行,这里的线程池就是我们最熟悉的“http-nio-8080-exec-N”线程了。

SocketProcessor的doRun中会首先检查连接的握手状态,如果还没握手成功,会尝试进行握手。如果握手成功将socketWrapper交由ConnectionHandler的process方法进行处理。到这里TCP层的差异化处理基本完成。

ConnectionHandler的process方法代码比较多,但总结起来其实就是两步:1、获取Processor,获取不到则通过Http11NioProtocol创建一个(会同时创建Tomcat Request、Response)2、调用Processor的process方法来处理socketWrapper对象。

Http协议处理

在上一步中创建的Processor实际是Http11Processor,而Http11Processor做的其实就是HTTP1.1协议数据的解析。

在Http11Processor的process方法中,主要是根据SocketEvent来路由不同的操作,当是一个新请求的时候将会是OPEN_READ,也就会执行协议处理器的service方法。这里将做解析请求头、协议版本的检查、设置过滤器等操作,之后会将Request、Response交由CoyoteAdapter进行处理。

http协议处理

CoyoteAdapter是一个适配器,将Tomcat Request,Response转变为Servlet Request、Response,这里会做一个比较重要的操作,就是根据请求信息(主要是请求的uri)决定请求处理的Host、Context、Wrapper,然后通过如下代码将请求交由Tomcat容器处理。

connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);

容器处理过程

在容器的初始化过程可以通过设置Valve来做一系列处理,比如记录请求日志、请求过滤等,每个容器的都会有一个Pipeline通过责任链模式串联起所有的Valve,而请求过来就会经过如下过程,才会最终到达Servelt。

容器流程

在StandardHostValve中不会做其他处理,只是简单的找到请求映射的Context(上文已经找到),并将对象交由Context的Valve进行处理。

在StandardContextValve中也未做复杂操作,只是做了一些简单的过滤,也将请求对象交由下个容器处理。

StandardContext处理

StandardWrapperValve中会首先请求Wrapper来分配处理这个请求的Servlet实例,然后通过ApplicationFilterFactory创建一个ApplicationFilterChain。创建的ApplicationFilterChain会持有上文分配的Servlet示例,并且将符合条件的FilterConfig加入。

接着会调用ApplicationFilterChain的doFilter方法,会首先根据FilterConfig获取Filter对象,然后执行Filter的doFilter方法(过滤器的执行),然后会调用持有Servlet对象的service方法。到这里请求也就到了我们编写的Servlet中了。