Tomcat 请求的接收处理

当一个请求(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中了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容