Tomcat源码解析——一次请求的处理流程

        在上一篇文章中,我们知道Tomcat在启动后,会在Connector中开启一个Acceptor(接收器)绑定线程然后用于监听socket的连接,那么当我们发出请求时,第一步也就是建立TCP连接,则会从Acceptor的run方法处进入。

Acceptor: public void run() {int errorDelay = 0;//只有运行状态才进入while循环while (running) {while (paused && running) {state = AcceptorState.PAUSED;try {Thread.sleep(50);} catch (InterruptedException e) {// Ignore}}if (!running) {break;}state = AcceptorState.RUNNING;try {countUpOrAwaitConnection();Socket socket = null;try {//阻塞接受TCP连接socket = serverSocketFactory.acceptSocket(serverSocket);} catch (IOException ioe) {//...省略}errorDelay = 0;//设置socket参数if (running && !paused && setSocketOptions(socket)) {//处理该socket(关键)if (!processSocket(socket)) {countDownConnection();//处理不了则关闭socketcloseSocket(socket);}} else {countDownConnection();closeSocket(socket);}} catch (IOException x) {if (running) {log.error(sm.getString("endpoint.accept.fail"), x);}} catch (NullPointerException npe) {if (running) {log.error(sm.getString("endpoint.accept.fail"), npe);}} catch (Throwable t) {ExceptionUtils.handleThrowable(t);log.error(sm.getString("endpoint.accept.fail"), t);}}state = AcceptorState.ENDED;}protected boolean processSocket(Socket socket) {try {SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());wrapper.setSecure(isSSLEnabled());// During shutdown, executor may be null - avoid NPEif (!running) {return false;}//包装成一个socket执行器然后丢入线程池中处理(服务器是一对多的,所以不能阻塞TCP接收线程,必须异步处理)getExecutor().execute(new SocketProcessor(wrapper));} catch (RejectedExecutionException x) {log.warn("Socket processing request was rejected for:"+socket,x);return false;} catch (Throwable t) {ExceptionUtils.handleThrowable(t);log.error(sm.getString("endpoint.process.fail"), t);return false;}return true;}

        在Acceptor的run方法中进行while循环,然后阻塞接受一个TCP的连接,设置一些参数后包装成一个SocketProcessor丢入线程池中异步执行(因为服务器是一对多的,不能阻塞当前的接收TCP连接)。

SocketProcessor:public void run() {boolean launch = false;synchronized (socket) {try {SocketState state = SocketState.OPEN;try {//SSL的预处理// SSL handshakeserverSocketFactory.handshake(socket.getSocket());} catch (Throwable t) {//省略}if ((state != SocketState.CLOSED)) {if (status == null) {//交给对应的协议处理器去处理,因为Tomcat还支持AJP协议state = handler.process(socket, SocketStatus.OPEN_READ);} else {state = handler.process(socket,status);}}//处理过程中状态被设置为关闭则直接关闭socketif (state == SocketState.CLOSED) {countDownConnection();try {socket.getSocket().close();} catch (IOException e) {// Ignore}}//...省略} finally {if (launch) {//如果不能处理,则兜底关闭连接handler.process(socket, SocketStatus.DISCONNECT);}}}Http11ConnectionHandler:public SocketState process(SocketWrapper<S> wrapper,SocketStatus status) {//拿到对应的socket(因为Tomcat支持NIO、JIO、APR三种运行模式,所以需要用泛型)S socket = wrapper.getSocket();if (socket == null) {// Nothing to do. Socket has been closed.return SocketState.CLOSED;}//拿到最终的协议处理器Processor<S> processor = connections.get(socket);if (status == SocketStatus.DISCONNECT && processor == null) {return SocketState.CLOSED;}//...省略try {//真正的处理SSLinitSsl(wrapper, processor);SocketState state = SocketState.CLOSED;do {if (status == SocketStatus.CLOSE_NOW) {processor.errorDispatch();state = SocketState.CLOSED;//...省略} else {//此处根据状态最终调用处理state = processor.process(wrapper);}}//...省略}}Http11Processor:public SocketState process(SocketWrapper<S> socketWrapper)throws IOException {//获取IOsetSocketWrapper(socketWrapper);getInputBuffer().init(socketWrapper, endpoint);getOutputBuffer().init(socketWrapper, endpoint);//基本请求参数设值,keepAlive默认是truekeepAlive = true;comet = false;openSocket = false;sendfileInProgress = false;readComplete = true;try {setRequestLineReadTimeout();//解析请求行 HTTP协议三部分(请求首行、请求头、请求体,其中请求体一般是用户自己使用时解析)if (!getInputBuffer().parseRequestLine(keptAlive)) {if (handleIncompleteRequestLineRead()) {break;}}//读取请求头(只是把请求头解析成Map形式,并没有根据里面的值做一些处理,也就是初步解析)if (!getInputBuffer().parseHeaders()) {openSocket = true;readComplete = false;break;}    if (!getErrorState().isError()) {try {//真正的解析请求头,解析请求头每个Key里面的value,常用的则校验数据格式等prepareRequest();} catch (Throwable t) {//...省略}}//如果没有错误,则继续处理if (!getErrorState().isError()) {try {rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);//调用适配器转交给最顶级容器(Engine)处理(关键)adapter.service(request, response);if(keepAlive && !getErrorState().isError() && (response.getErrorException() != null ||(!isAsync() &&statusDropsConnection(response.getStatus())))) {setErrorState(ErrorState.CLOSE_CLEAN, null);}setCometTimeouts(socketWrapper);} catch (InterruptedIOException e) {setErrorState(ErrorState.CLOSE_NOW, e);} catch (HeadersTooLargeException e) {if (response.isCommitted()) {setErrorState(ErrorState.CLOSE_NOW, e);} else {}} catch (Throwable t) {//...省略}}rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);if (!isAsync() && !comet) {if (getErrorState().isError()) {getInputBuffer().setSwallowInput(false);} else {checkExpectationAndResponseStatus();}//结束一个请求endRequest();}//设置状态和一些响应信息rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT);if (getErrorState().isError()) {response.setStatus(500);}request.updateCounters();if (!isAsync() && !comet || getErrorState().isError()) {if (getErrorState().isIoAllowed()) {getInputBuffer().nextRequest();getOutputBuffer().nextRequest();}}if (!disableUploadTimeout) {if(endpoint.getSoTimeout() > 0) {setSocketTimeout(endpoint.getSoTimeout());} else {setSocketTimeout(0);}}rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);if (breakKeepAliveLoop(socketWrapper)) {break;}}//...省略}Adapter:public void service(org.apache.coyote.Request req,org.apache.coyote.Response res)throws Exception {
//解析其它的HTTP参数,如?param=value(查询参数)、session和cookie处理、根据URL找到对应的Host和Contxt、Wrapper容器(因为要符合Servlet规范,很多参数都需要Tomcat为用户解析好)
boolean postParseSuccess = postParseRequest(req, request, res, response);
//真正转发给容器处理               
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);}

        在SocketProcessor的run方法中,异步的进行解析,用对应的协议处理器处理,然后开始解析请求首行,请求头,等到解析完成并且没有错误时,则通过Adapter(适配器)的service方法,将请求传递给容器(Engine、Host、Context、Wrapper)。

        在组件介绍的文章中,我们知道Engine、Host、Context、Wrapper都属于容器Container,有共同的特性,如一样的生命周期、都有Pipeline(管道)、管道中都存在Valve(阀)。

        在Adapter的service方法中,获取的是Engine中Pipeline的第一个Valve去处理。

        Pipeline中有两种Valve,一种是普通Valve,一种是基础Valve。普通Valve也就是用户自定义添加使用的,而基础Vavle是用于当前容器兜底和传递给下一个容器使用的,所以在Adapter中传递下去的Request和Response最终要从上至下经过一个个的容器中的Vavle处理。

StandardEngineValve:public final void invoke(Request request, Response response)throws IOException, ServletException {//从请求中获取对应的HostHost host = request.getHost();//调用Host的Valvehost.getPipeline().getFirst().invoke(request, response);}StandardHostValve:public final void invoke(Request request, Response response)throws IOException, ServletException {Context context = request.getContext();if( context.getLoader() != null ) {//此处为当前线程设置应用的类加载器,从而实现应用之间class隔离(重点)Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());}//...省略//调用Context的Valvecontext.getPipeline().getFirst().invoke(request, response);}StandardContextValve:public final void invoke(Request request, Response response)throws IOException, ServletException {//确认请求response.sendAcknowledgement();//...省略//传递到最后一个Wrapper容器中处理wrapper.getPipeline().getFirst().invoke(request, response);}StandardWrapperValve:public final void invoke(Request request, Response response)throws IOException, ServletException {boolean unavailable = false;StandardWrapper wrapper = (StandardWrapper) getContainer();Servlet servlet = null;Context context = (Context) wrapper.getParent();if (!unavailable) {//通过Wrapper容器获取或创建一个Servletservlet = wrapper.allocate();}//...省略//根据请求信息创建一条过滤器链ApplicationFilterFactory factory =ApplicationFilterFactory.getInstance();ApplicationFilterChain filterChain =factory.createFilterChain(request, wrapper, servlet);try {//调用过滤器链处理请求,这就是我们经常配置的Filter过滤器filterChain.doFilter(request.getRequest(), response.getResponse());} catch (Throwable e) {//异常处理exception(request, response, e);}}StandardWrapper:public Servlet allocate() throws ServletException {if (!singleThreadModel) {//此处可以看到使用的是双重校验加锁的单例模式,所以Wrapper容器其实就是Servlet的包装,一个Wrapper容器对应一个单例的Servletif (instance == null) {synchronized (this) {if (instance == null) {//通过反射创建自定义的Servlet对象instance = loadServlet();if (!singleThreadModel) {newInstance = true;countAllocated.incrementAndGet();}}}}//如果没有初始化则初始化if (!instanceInitialized) {initServlet(instance);}}private synchronized void initServlet(Servlet servlet)throws ServletException {//初始化调用Servlet的init方法servlet.init(facade);}ApplicationFilterChain:public void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {internalDoFilter(request,response);}private void internalDoFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {ApplicationFilterConfig filterConfig = filters[pos++];Filter filter = null;try {filter = filterConfig.getFilter();//调用过滤器执行filter.doFilter(request, response, this);} catch (Throwable e) {//省略...异常处理}if ((request instanceof HttpServletRequest) &&(response instanceof HttpServletResponse)) {//最终调用到用户自定义的Servlet的service方法中servlet.service(request, response);}}    

         Engine把Request通过PipeLine中的Valve传递到最终的Wrapper中,用户可以在这几个容器(Engine、Host、Context、Wrapper)的PipeLine添加普通的Valve进行一些操作。

        终于,在Wrapper中,Tomcat通过单例模式创建出用户自定义的Servlet,然后经过过滤器链的处理后,调用到Servlet的service方法中,此方法也是给用户实现处理请求的最终方法。

        时序图:

                

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/2916.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用CSS+HTML完成导航栏

HTML <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>导航栏示例</title> &l…

07 内核开发-避免命名冲突经验技巧分享

07 内核开发-避免命名冲突经验技巧分享 目录 07 内核开发-避免命名冲突经验技巧分享 1.如何在内核开发过程中&#xff0c;避免命名冲突 2. 背景 3.避免方法 4.了解下 文件/proc/kallsyms 5.总结 课程简介&#xff1a; Linux内核开发入门是一门旨在帮助学习者从最基本的…

java-异常

一、异常的概念及分类 Exception&#xff1a;异常&#xff0c;代表程序可能出现的问题 Exception分为两类&#xff1a; 1、运行时异常&#xff1a;RuntimeException以及其子类&#xff0c;编译阶段不会出现异常提醒&#xff0c;在运行阶段会出现异常提醒 2、编译时异常&…

基于SpringBoot+Vue网上商城系统的设计与实现

系统介绍 随着社会的不断进步与发展&#xff0c;人们经济水平也不断的提高&#xff0c;于是对各行各业需求也越来越高。特别是从2019年新型冠状病毒爆发以来&#xff0c;利用计算机网络来处理各行业事务这一概念更深入人心&#xff0c;由于用户工作繁忙的原因&#xff0c;去商…

抽象工厂模式设计实验

【实验内容】 楚锋软件公司欲开发一套界面皮肤库&#xff0c;可以对 Java 桌面软件进行界面美化。为了保护版权&#xff0c;该皮肤库源代码不打算公开&#xff0c;而只向用户提供已打包为 jar 文件的 class 字节码文件。用户在使用时可以通过菜单来选择皮肤&#xff0c;不同的…

Java数据类型以及范围

数据类型&#xff1a; 取值范围&#xff1a; 取值&#xff1a;

磁性呼吸传感技术与机器学习结合在COVID-19审断中的应用

介绍 呼吸不仅是人类生存的基础&#xff0c;而且其模式也是评估个体健康状态的关键指标。异常的呼吸模式往往是呼吸系统疾病的一个警示信号&#xff0c;包括但不限于慢性阻塞性肺病&#xff08;COPD&#xff09;、阻塞性睡眠呼吸暂停&#xff08;OSA&#xff09;、肺炎、囊性纤…

idea连接Docker数据库

我们在docker下创建了数据库&#xff0c;想要更方便的查看和操作该数据库&#xff0c;idea和DataGrip或者其他软件都可以。在数据库连接时需要填写数据库名字&#xff0c;主机&#xff0c;端口&#xff0c;数据库用户名和密码。 输入之后先不要点击OK和按Enter键&#xff0c;我…

GAN详解,公式推导解读,详细到每一步的理论推导

在看这一篇文章之前&#xff0c;希望熟悉掌握熵的知识&#xff0c;可看我写的跟熵相关的一篇博客https://blog.csdn.net/m0_59156726/article/details/138128622 1. GAN 原始论文&#xff1a;https://arxiv.org/pdf/1406.2661.pdf 放一张GAN的结构&#xff0c;如下&#xff1…

Linux:动静态库介绍

动静态库 库的介绍开发环境 & 编译器库存在的意义库的实现库的命名静态库制作和使用总结 动态库的制作和使用动态库的使用方法方法一方法二方法三 库加载问题静态库加载问题动态库的加载问题与位置无关码 C/C静态库下载方式 库的介绍 静态库&#xff1a;程序在编译链接的时…

计算机网络---第十一天

生成树协议 stp作用&#xff1a; 作用&#xff1a;stp用于解决二层环路问题。 BPDU&#xff1a; 含义&#xff1a;桥协议数据单元&#xff0c;用于传递stp协议相关报文 分类&#xff1a;配置bpdu---用于传递stp的配置信息 tcn bpdu---用于通告拓扑变更信息 包含信息&…

数据库主键ID自增,两种方法获取插入数据库那条数据自动生成的主键ID值

目录 1. 前言 2. 适用于 MyBatis 框架 2.1 获取单条插入语句生成的ID 2.2 获取集合插入生成的多条数据的ID 3. 适用于 MyBatisPlus 框架 3.1 获取单条数据插入生成的ID 3.2 获取集合插入数据生成的多条数据的ID 4. 小结 1. 前言 在开发过程中&#xff0c;我们可能会遇…

OpenCompass 大模型评测实战——作业

OpenCompass 大模型评测实战——作业 一、基础作业1.1、使用 OpenCompass 评测 internlm2-chat-1_8b 模型在 C-Eval 数据集上的性能1.1.1、安装基本环境1.1.2、解压数据集1.1.3、查看支持的数据集和模型1.1.4、启动评测 二、进阶作业2.1、将自定义数据集提交至OpenCompass官网 …

2024春季春日主题活动策划方案

2024解冻派对“春日浪漫”主题活动策划方案-32P 方案页码&#xff1a;32页 文件格式&#xff1a;pptx 方案简介&#xff1a; 春来一季&#xff0c;新生欢喜 花香丨微风丨阳光 活动唤起【春日浪漫记忆】&#xff01; 年轻人不一样的派对活动 可以与朋友/小朋友/家人互动…

深度学习-线性代数

目录 标量向量矩阵特殊矩阵特征向量和特征值 标量由只有一个元素的张量表示将向量视为标量值组成的列表通过张量的索引来访问任一元素访问张量的长度只有一个轴的张量&#xff0c;形状只有一个元素通过指定两个分量m和n来创建一个形状为mn的矩阵矩阵的转置对称矩阵的转置逻辑运…

03-JAVA设计模式-访问者模式

访问者模式 什么是访问者模式 访问者模式&#xff08;Visitor Pattern&#xff09;是软件设计模式中的一种行为模式&#xff0c;它用于将数据结构中的元素与操作这些元素的操作解耦。这种模式使得可以在不修改数据结构的情况下添加新的操作。 在访问者模式中&#xff0c;我们…

图文教程 | Git安装配置、常用命令大全以及常见问题

前言 因为多了一台电脑&#xff0c;平时写一些代码&#xff0c;改一些文件&#xff0c;用U盘存着转来转去特别麻烦。于是打算用Git管理我的文件&#xff0c;方便在两个终端之间传输数据啥的。也正好给新电脑装好Git。 &#x1f4e2;博客主页&#xff1a;程序源⠀-CSDN博客 &…

HFSS端口介绍2---波端口

前面我们讨论了Lumped Port设定相关的内容,这节我们继续讨论Wave Port(波端口)使用相关的问题。 波端口使用范围 封闭结构:如波导、同轴电缆等 包含多个传播模式的模型 端口平面在求解区域外的模型 模型中包含均匀的波导或者传输线结构 波端口的大小 对于封闭的传输线结构:边…

视频教程下载:用ChatGPT的 API 开发AI应用指南

通过这门关于 OpenAI API 和 ChatGPT API 的全面课程&#xff0c;在您的应用中释放人工智能的力量。随着人工智能技术的快速发展&#xff0c;比以往任何时候都更重要的是保持领先地位&#xff0c;并为您的项目利用这些尖端工具。在本课程中&#xff0c;您将深入了解人工智能驱动…

物联网硬件设计开发全攻略:十大关键阶段深度解析

为物联网应用设计开发高效稳定的硬件系统本身是一项既复杂又精细的艰巨任务。看似小巧的物联网设备一般由软件、固件和硬件组件组成&#xff0c;其中&#xff0c;硬件组件更是占据了约80%的成本与开发挑战。那么&#xff0c;为何硬件部分如此棘手&#xff1f;在这篇文章中&…