Tomcat原理系列之三:对请求的过程详细分析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Tomcat原理系列之三:对请求的过程详细分析

请求的处理是整个Tomcat的核心。深入了解Tomcat的请求过程,对于我们理解我们的应用项目,对于我们解决问题,对于我们今后开发项目都有深远的影响

如果看过Tomcat原理系列之二:由点到线,请求主干;一定对请求链具体走了哪些组件有了印象。我们再进一步拆解请求链,将链上涉及的类一一道来。


Connector


Connector组件作为Server的一部分,主要用于接收,解析http请求,并将请求封装成requset交给Container容器进行处理. Connector是如何接受请求的呢?


Connector使用ProtocolHandler(协议处理器)来处理请求的, 不同的ProtocolHandler处理不同模式的请求类型. 例如: Http11Protocol支持BIO类型的Socket来连接的, Http11NioProtocol支持NIO类型的NioSocket来连接的 ((基于Tomcat8)因为Tomcat高版本默认使用NIO模式,本文以NIO类型的处理来讲)


image.png


Http11NioProtocol:

通过Http11NioProtocol的构造方法. 我们可以看出,主要包括两大部分:Endpoint:端点, 也就是socket的请求的入口.ConnectionHandler:  Connection 处理器


public Http11NioProtocol() {
        endpoint=new NioEndpoint();//端点
        cHandler = new Http11ConnectionHandler(this);//connection处理器
        ((NioEndpoint) endpoint).setHandler(cHandler);
        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
    }


1.Endpoint的NIO实现NioEndpoint

NioEndpoint是整个Tomcat的请求的入口. NioEndponit类中有个几个内部类非常重要,也是请求的必经之地.我们按照请求的走过的顺序一个个的解开看.


image.png


(不同版本的Tomcat可能有所不同,但是基本框架是类似的.)

1. Acceptor线程:

接收socket线程.  Acceptor本身就是一个线程,run()方法里有while循环执行 serverSock.accept()接收线程.  此处有一个点要注意, 虽然是基于NIO的Endpoint但是这里还是阻塞式接收socket连接的方法. 也就是说会阻塞到serverSock.accept();

(1)当获取到SocketChannel对象后, 调用setSocketOptions(socket)方法, 将SocketChannel封装到NioChannel对象中. (2)并调用Poller的register(Niochannel socket)方法,将NioChannel进一步封装到NioSocketWrapper对象中, (3)最后在将NioSocketWrapper对象封装到PollerEvent对象压到Pollerevents队列里中去.

这里是一个典型的生产-消费者模式Acceptor 是events queue的生产者, Poller是envets queue的消费者.

(代码有删减,只把重要的提取出来)

image.png

@Override
       public void run() {
           while (running) {
                   try {
                       //接收线程(没有连接时阻塞在此处)
                       socket = serverSock.accept();
                   } catch (IOException ioe) {
                   }
                   // Successful accept, reset the error delay
                   errorDelay = 0;
                   // Configure the socket
                   if (running && !paused) {
                       // setSocketOptions()======= 对socket进一步处理
                       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;
       }
2. Poller线程:

主要用于以较少的资源轮询已连接套接字以保持连接,当数据可用时转给worker工作线程. Poller线程run方法,while循环消费events queue里的PollerEvent事件 . 通过 event()方不断取出PollerEvent对象,然后将PollerEvent对象中的NioSocketWrapper包装类OP_READ事件注册到Poller的Selector选择器去.{此处我们才真正看到NIO的影子} 在注册完OP_READ事件后. 紧接着执行**selector.selectedKeys()**方法将就绪的通道key返回,然后通过selectedKey访问就绪的通道. 取出NioSocketWrapper对象. 接着调用Poller的processKey()方法,根据sk是OPEN_READ事件或者OPEN_WRITE事件,调用NioEndpoint.processSocket()方法将NioSocketWrapper封装到SocketProcessor对象中, 并提交到NioEndpoint.executor线程池(Worker线程池)去执行

@Override
       public void run() {
           // Loop until destroy() is called
           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
                           keyCount = selector.selectNow();
                       } else {
                           keyCount = selector.select(selectorTimeout);
                       }
                       wakeupCounter.set(0);
                   }
                   if (close) {
                       events();
                       timeout(0, false);
                       try {
                           selector.close();
                       } catch (IOException ioe) {
                           log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe);
                       }
                       break;
                   }
               } catch (Throwable x) {
                   ExceptionUtils.handleThrowable(x);
                   log.error("",x);
                   continue;
               }
               //either we timed out or we woke up, process events first
               if ( keyCount == 0 ) hasEvents = (hasEvents | events());
               Iterator<SelectionKey> iterator =
                   keyCount > 0 ? selector.selectedKeys().iterator() : null;
               // Walk through the collection of ready keys and dispatch
               // any active event.
               while (iterator != null && iterator.hasNext()) {
                   SelectionKey sk = iterator.next();
                   NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
                   // Attachment may be null if another thread has called
                   // cancelledKey()
                   if (attachment == null) {
                       iterator.remove();
                   } else {
                       iterator.remove();
                       processKey(sk, attachment);
                   }
               }//while
               //process timeouts
               timeout(keyCount,hasEvents);
           }//while
           getStopLatch().countDown();
       }


3.Worker线程组:

SocketProcessor处理socket连接的任务提交到worker线程池去执行

public boolean processSocket(SocketWrapperBase<S> socketWrapper,
           SocketEvent event, boolean dispatch) {
       try {
           if (socketWrapper == null) {
               return false;
           }
           SocketProcessorBase<S> sc = processorCache.pop();
           if (sc == null) {
               sc = createSocketProcessor(socketWrapper, event);
           } else {
               sc.reset(socketWrapper, event);
           }
           Executor executor = getExecutor();
           if (dispatch && executor != null) {
               executor.execute(sc);//将socket处理任务提交到worker线程池
           } else {
               sc.run();
           }
       } catch (RejectedExecutionException ree) {
           getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
           return false;
       } catch (Throwable t) {
           ExceptionUtils.handleThrowable(t);
           // This means we got an OOM or similar creating a thread, or that
           // the pool and its queue are full
           getLog().error(sm.getString("endpoint.process.fail"), t);
           return false;
       }
       return true;
   }
4.SocketProcessor类:

NioEndpoint.SocketProcessor类作为一个任务, run()方法中,将socket的包装类NioEndpoint.NioSocketWrapper交给ProtocolHandler协议处理的ConnectionHandler进行处理


2.ConnectionHandler连接处理器

NioSocketWrapper 此时来到 ConnectionHandler这里.

Http11Processor Http协议处理器(http协议实现)

ConnectionHandler.process()方法,首先得到一个Http11Processor 处理器, 然后会将NioSocketWrapper交给Http11Processor 的进行处理

在这里我们回顾一个经典的问题:HTTP协议的组成部分,或者叫做HTTP协议的报文组成 1.请求行 2.请求头 3.空行 4.消息体

image.png


Http11Processor.service()方法中就是HTTP协议实现的地方. Http11Processor会创建一个Http11InputBuffer对象. Http11InputBuffer中的parseRequestLine、parseHeaders 和 parseHeader方法 会分别读取[请求行,请求头]

请求的读取去哪里了呢?? 对请求体的读取却不在这里. 而是将请求体的解析与读取延迟到Servlet中去了. 在调用getParameter,getParameterMap,getParameterNames,getParameterValues时会先调用parseParameters()方法解析请求参数. 这个放到以后再说.

Http11Processor的父类在初始化的时候,会创建Request对象,Response对象. 解析完请求行,请求头后. NioSocketWrapper对象此时变成了Request对象. 然后就开始下一程.


image.png

CoyoteAdapter:(Request,Response)


Http11Processor.service()方法处理后, 会调用CoyoteAdapter.service(request,repose) 对request,repose做一些预处理后,又开始了下一程connector.getService().getContainer().getPipeline().getFirst().invoke( request, response);


Container:(Request,Response)


Request,Response 对象层层经过Engine,Host,Context,Wrapper的valve.

Valve

Valve作为一个个基础的阀门,扮演着业务实际执行者的角色.


image.png


1.StandardWrapperValve(最后一个valve)

在StandardWrapperValve.invoke方法中会根据配置的Filter过滤器创建ApplicationFilterChain过滤器链.

ApplicationFilterChain filterChain =
              ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

然后执行过滤器链的doFilter方法

filterChain.doFilter(request.getRequest(),
                                  response.getResponse());
2.ApplicationFilterChain

ApplicationFilterChain使用责任了模式,执行过滤器的doFilter方法. 当执行完最后一个Filter方法后. 调用

servlet.service(request, response);

此时到了我们常见的servlet了.终于到了我们的业务代码了


总结:


socket ----->Connector接收socket连接封装成NioSocketWrapper对象----->ConnectionHandler取得HTTP协议内容,创建Requset,Reponse对象----->Container携带Requset,Reponse对象层级执行valve的invoke方法,到达最后一个StandardWrapperValve, 创建ApplicationFilterChain过滤器链, ----->ApplicationFilterChain过滤器链执行doFilter方法,执行完成后调用servlet.service()方法 ----->业务代码

个人理解有误差,望指出. Tomcat中涉及的细节很多.需要慢慢品味. 但是请求的大体行动路线,就如上描述的那样. 希望读者不断的去看源码挖掘更深的细节. 学习Tomact优秀的设计


相关文章
|
6月前
|
应用服务中间件
从零手写实现 tomcat-03-请求和响应的抽象
该文档介绍了 MiniCat 项目,它是一个简单的 HTTP 服务器实现。v1 版本中, MiniCatRequest 对象解析 HTTP 请求,包括方法、URL 和输入流,而 MiniCatResponse 使用输出流处理响应。start 方法使用这些封装后的对象处理网络通信。在 v2 版本,服务器添加了返回静态资源文件的功能,如 HTML,通过解析 URL 并读取对应本地文件内容来响应请求。测试示例展示了如何访问和显示 index.html。
|
2月前
|
监控 网络协议 应用服务中间件
【Tomcat源码分析】从零开始理解 HTTP 请求处理 (第一篇)
本文详细解析了Tomcat架构中复杂的`Connector`组件。作为客户端与服务器间沟通的桥梁,`Connector`负责接收请求、封装为`Request`和`Response`对象,并传递给`Container`处理。文章通过四个关键问题逐步剖析了`Connector`的工作原理,并深入探讨了其构造方法、`init()`与`start()`方法。通过分析`ProtocolHandler`、`Endpoint`等核心组件,揭示了`Connector`初始化及启动的全过程。本文适合希望深入了解Tomcat内部机制的读者。欢迎关注并点赞,持续更新中。如有问题,可搜索【码上遇见你】交流。
【Tomcat源码分析】从零开始理解 HTTP 请求处理 (第一篇)
|
1月前
|
前端开发 Java 应用服务中间件
21张图解析Tomcat运行原理与架构全貌
【10月更文挑战第2天】本文通过21张图详细解析了Tomcat的运行原理与架构。Tomcat作为Java Web开发中最流行的Web服务器之一,其架构设计精妙。文章首先介绍了Tomcat的基本组件:Connector(连接器)负责网络通信,Container(容器)处理业务逻辑。连接器内部包括EndPoint、Processor和Adapter等组件,分别处理通信、协议解析和请求封装。容器采用多级结构(Engine、Host、Context、Wrapper),并通过Mapper组件进行请求路由。文章还探讨了Tomcat的生命周期管理、启动与停止机制,并通过源码分析展示了请求处理流程。
|
1月前
|
负载均衡 应用服务中间件 Apache
Tomcat负载均衡原理详解及配置Apache2.2.22+Tomcat7
Tomcat负载均衡原理详解及配置Apache2.2.22+Tomcat7
37 3
|
3月前
|
Arthas Java 应用服务中间件
一次Tomcat返回404的分析
一个Web应用部署在阿里云EDAS上,使用Tomcat 7.0.59.3,在测试环境遭遇所有接口返回404的问题,而生产环境正常。测试与生产环境主要差异在于Apollo配置不同。通过Arthas工具监控,确认Spring已正确加载Controller,并且请求未进入Spring或Filter处理流程。进一步分析发现,Tomcat内部处理流程中设置了404状态码,最终定位到`org.apache.coyote.http11.AbstractHttp11Processor.process`方法存在问题。通过对代码逻辑的分析,确定原因是请求URL路径不正确。修正URL路径后问题得到解决。
82 1
一次Tomcat返回404的分析
|
5月前
|
缓存 负载均衡 NoSQL
Redis系列学习文章分享---第十四篇(Redis多级缓存--封装Http请求+向tomcat发送http请求+根据商品id对tomcat集群负载均衡)
Redis系列学习文章分享---第十四篇(Redis多级缓存--封装Http请求+向tomcat发送http请求+根据商品id对tomcat集群负载均衡)
82 1
|
5月前
|
应用服务中间件
tomcat服务器get、post请求及响应中文乱码问题
tomcat服务器get、post请求及响应中文乱码问题
|
5月前
|
Java 应用服务中间件 API
Tomcat处理一个HTTP请求的执行流程的详细解析
Tomcat处理一个HTTP请求的执行流程的详细解析
163 4
|
4月前
|
应用服务中间件
tomcat8.5处理get请求时,控制台输出中文乱码问题的解决
tomcat8.5处理get请求时,控制台输出中文乱码问题的解决
53 0
|
6月前
|
安全 Java 应用服务中间件
【小白误闯】这可能是对 Tomcat 工作原理解释最详细的文章
脑子一闪而过,当年 V 哥在面试 Java 开发时,被问到让你写一个 Tomcat 服务器,你有什么想法?尼码,面试官摆明是在压工资了,你得逞了,我回答不上来,当时也没研究过 Tomcat 的源码,饮恨被拒。今天想想看,当时尴尬的表情,蛮逗的嘞。 今天V 哥有空把这个问题整理出来,干脆写成文章吧,放到资料库里,也分享给大家。Tomcat 是一个流行的 Java Servlet 和 JSP 容器,用于运行 Java Web 应用程序。它的核心组件主要包括: