[.com原稿]在互联网技术飞速发展的今天,各种网络应用层出不穷。Tomcat作为Web应用的容器,承载着Web请求处理和响应的工作。图片来自Pexels。大部分企业的Web应用都运行在上面。Tomcat是程序员的老朋友了,那么今天就带大家去看看这位老朋友,看看它是如何处理Web请求的,以及它的内部架构,对于帮助我们理解Tomcat的使用大有裨益。在本文中,您将学习到以下内容:Web容器与TomcatTomcat连接器Tomcat容器Web容器与Tomcat要解释清楚Tomcat是做什么的,首先要从早期的Web应用说起。图1:浏览器通过HTTP服务器获取静态资源如图1所示。一开始,用户通过浏览器查看新闻等静态资源。这时候就需要将静态的HTML资源通过HTTP服务器返回给浏览器,浏览器将解析后的HTML呈现给用户。这里的web容器是用来存放HTTP服务器的,它可以处理网络请求并做出响应。随着互联网的发展,用户需求已经从静态资源转向动态资源的获取。同时浏览器在获取资源的同时也会和服务器进行交互。于是,Web容器的功能开始扩展。除了能够处理HTTP请求外,还需要HTTP服务器调用服务器程序,也就是常说的Web应用程序。图2:通过HTTP服务器调用Web应用程序为满足这一要求,Sun引入了Servlet技术,这是一种运行在服务器端的Javaapplet。由于Servlet不能独立运行,需要将其托管在一个Servlet容器中,对其进行初始化、启动等管理操作。图3:Servlet容器的介绍如图3所示,为了满足用户日益增长的需求,将Servlet作为Web应用添加到Web容器中,为用户提供动态资源。为了承载Servlet,也加入了Servlet容器,但是每个Servlet代表一个业务类,包括一些业务应用。如果都接入Web容器,用户需要遵循统一的接口,提供统一的服务响应。说白了,就是需要遵守一定的规则,才可以放到Servlet容器中,方便管理。那么这条规则就是Servlet接口。从图中可以看出,Servlet接口将单个Servlet标准化。图4:Servlet接口如图4所示,对于Servlet接口,定义了init方法用于Servlet资源的初始化,还定义了destroy方法用于Servlet资源的释放。其中,Service方法用于实现具体的业务需求。可以看到这个方法传入了两个参数ServletRequest和ServletResponse,分别代表了对用户请求信息和Servlet响应信息的封装。后面我们会介绍,SpringMVC在Tomcat中运行时也是作为一个Servlet存在的,而SpringMVC容器是由DispatcherServlet在init方法中创建的。接口中的getServletConfig方法将返回ServletConfig。ServletConfig用于封装Servlet的初始化参数。可以在web.xml中配置Servlet参数,然后通过getServletConfig方法获取参数。上面已经介绍了Servlet接口,接下来通过图5,我们将深入了解Servlet接口调用的外围类。图5:Servlet类关系图如图5所示,Servlet接口依赖于ServletConfig接口,ServletConfig接口只是用来处理Servlet配置参数,而ServletConfig接口还会关联ServletContext获取Servlet上下文信息。Servlet接口中的服务方法依赖于两个参数,ServletRequest和ServletResponse。同时有两个接口,HttpServletRequest和HttpServletResponse,会分别继承ServletRequest和ServletResponse。一般来说,Servlet作为一个接口需要一个具体的实现类来实现这个接口,所以Servlet规范提供了一个抽象类GenericServlet,它实现了Servlet。然后是继承GenericServlet的HttpServlet类,它也依赖于HttpServletRequest和HttpServletResponse来处理HTTP请求。Servlet接口定义是Servlet容器的重要组成部分,Servlet容器通过接口来管理连接的Servlet实体。接下来我们看一下Servlet容器的分类。这里,Servlet容器按照其工作模式分为3类:①独立运行的Servlet容器。在这种模式下,Servlet容器被用作Web服务器的一部分。这种模式在使用Javawebserver时也会用到,Tomcat的默认模式,如果不是基于Java的webservice,则需要使用以下两种模式。②内置Servlet容器Servlet容器由两部分组成:Web服务器插件和Java容器。Web服务器的内部地址空间需要开启一个JVM,在上面加载Java容器,运行servlet。当容器请求Servlet时,Web服务器插件会请求加载在JVM上的Servlet,通过JNI技术将请求传递给Java容器,再由Java容器将请求传递给Servlet进行处理。③外部Servlet容器Servlet容器运行在Web服务器的外部地址空间中。通过Web服务器插件在Web服务器的外部地址空间开启一个JVM加载Java容器运行Servlet。Web服务器插件和JVM使用IPC(进程间通信)机制进行通信。Web服务器通过IPC技术将请求传递给Java容器,然后Java容器将请求交给Servlet进行处理。了解了Servlet接口规范和Servlet容器后,我们就知道如果需要加载不同的动态资源(Web应用),就需要使用Servlet容器来加载对应的Servlet,那么这个加载过程是如何进行的呢?接下来我们看一下Servlet请求。和响应过程。图6:Servlet请求和响应流程如图6所示,HTTP请求和响应流程分为8个步骤:用户通过浏览器发起HTTP请求。Servlet容器收到请求后解析HTTP请求。根据解析的结果和配置信息创建一个Servlet实例。实例创建完成后,调用Servlet实例的init方法完成实例初始化。接下来就是调用Servlet中的Service方法来完成具体的业务。Service方法完成后,会将响应信息返回给Servlet容器。Servlet容器根据Servlet返回的信息创建HTTP响应并将其返回给浏览器。最后,Servlet容器调用destroy方法卸载Servlet,释放相应的资源。写到这里给大家做一个小节,web容器是用来提供web服务器的,当用户请求web容器时,会通过HTTP服务器解析HTTP请求,然后将解析后的请求交给服务小程序容器。Servlet容器负责定义Servlet接口规范和管理Servlet程序。所有的Web应用程序都会以Servlets的形式存在。每个Servlet都需要遵循Servlet接口的定义,按照Servlet处理流程响应用户请求。Tomcat就是我们展示的web容器,上面说的原理和流程都是Tomcat的功劳。简单来说,Tomcat=HTTP服务器+Servlet容器(Servlet接口规范),只要服务于Servlet接口的Servlet能够运行在Tomcat上,对外提供服务即可。Tomcat连接器上一节介绍了Tomcat的设计思想。Tomcat作为一个Web容器,实现了HTTP服务器和Servlet容器的功能,所以可以概括为两个功能:第一个是处理Socket连接,负责将网络请求解析成相应的Request和Response对象。第二个加载和管理Servlet,处理请求并返回响应。因此引入了Tomcat的两个核心组件:连接器(Connector)和容器(Container),其中连接器负责对外通信,容器负责内部Servlet的管理。图7:Tomcat的connector和container如图7所示,当浏览器有Request发给Tomcat时,connector会解析生成相应的ServletRequest,传递给container进行处理。容器处理完后,会返回连接器ServletResponse。同样,连接器会将ServletResponse解析成Response返回给浏览器。介绍完connector和container的关系,我们再重点介绍一下connector支持的三种应用层协议:HTTP/1.1协议:这是大多数web应用使用的访问协议,主要用于Tomcat单独运行时(未集成)与网络服务器)。AJP协议:用于与Web服务器(如ApacheHTTPServer)集成,实现对静态资源和集群部署的优化,目前支持AJP/1.3。HTTP/2.0协议:从Tomcat8.5和9.0开始支持下一代HTTP协议。目前最新的主流版本已经支持HTTP/2.0。上面了解了Tomcat接收网络请求的协议,下面介绍connector接收网络请求后的动作。Tomcat连接器会通过以下组件处理网络请求:①端点:作为连接器的通信端点,负责监听通信端口。它实现了Socket接收处理类,并对传输层进行了抽象。网络通信的I/O模型包括:非阻塞I/O、异步I/O或APR。应用层协议包括:HTTP、HTTPS、AJP。Endpoint机制其实是为了适应不同的IO模型和协议模型,所以它会提供三种通信实现:NioEndpoint(NIO)、AprEndpoint(APR)和Nio2Endpoint(NIO2)。②Processor:负责将接收到的网络请求构造成Request和Response对象,通过Adapter提交给容器处理。如果说Endpoint是用来实现TCP/IP协议的,那么Processor就是用来实现HTTP协议的,Processor可以理解为应用层的抽象。Processor是单线程的,Tomcat在同一个连接中复用Processor。Tomcat根据不同的协议提供了三个实现类:Http11Processor(HTTP/1.1)AjpProcessor(AJP)StreamProcessor(HTTP/2.0)同时,它还提供了两个实现:UpgradeProcessorInternal,用于处理内部支持的升级协议(如作为HTTP/2.0和WebSocket)。UpgradeProcessorExternal,用于处理外部扩展的升级协议支持。③ProtocolHandler:是Endpoint和Processor的抽象。由于Endpoint负责I/O模型,而Processor负责应用层协议,所以会有两者结合的方式一起工作,比如NIO+HTTP或者NIO2+AJP。所以两者是通过ProtocolHandler进行封装的,封装也是两者结合所带来的变化。例如:Http11NioProtocol和AjpNioProtocol。如图8所示,Tomcat设计了一个抽象基类来封装这部分,抽象基类AbstractProtocol实现了ProtocolHandler接口。图8:ProtocolHandler封装了Endpoint和Processor每个应用层协议都有自己的抽象基类,比如AbstractAjpProtocol和AbstractHttp11Protocol。④Adapter:负责请求的转换,将Tomcat的Request对象转换为ServletRequest对象。由于Tomcat可以加载任何符合Servlet接口规范的Servlet实例,因此需要使用ServletRequest对象与其进行通信,因此需要使用该组件来完成对请求对象的适配。前面介绍了Tomcat连接器的几个组件,这里总结一下连接器处理请求的过程,如图9所示。图9:Tomcat连接器用户通过浏览器向Tomcat发起请求,连接器收到通过Endpoint请求,通过I/O模型处理,然后将结果传递给Processor。Processor进行应用层协议处理后将请求传递给Adapter,Adapter将适配后的ServletRequest发送给容器进行处理。Tomcat容器上面我们提到了Tomcat的connector,它承载了处理网络IO请求和协议处理的工作,将请求适配发送给容器。接下来我们看一下Tomcat容器需要完成的工作。首先我们看一下容器的组成,如图10所示:图10:Tomcat容器Tomcat容器会包含一个Engine容器,而一个Engine容器可以包含多个Host容器,每个Host容器可以包含多个Context容器,也就是说Host可以包含多个应用,每个应用对应一个Context容器。作为一个应用程序,每个Context容器可以包含多个Wrapper容器:每个Wrapper容器包含一个Servlet容器,这意味着Tomcat允许一个应用程序有多个Servlet实现。介绍完Tomcat的容器组成,我们知道它由Engine、Host、Context和Wrapper组成,知道它们之间的包含关系。接下来,我们来看看每个组件需要完成的工作。Tomcat的四个容器结构相同,包括:Pipeline:用于处理request中的信息,每个Pipeline包含多个ValveValve,每个Valve都有相同的方法invoke(Requestrequest,Responseresponse),在这个方法中,可以处理请求中的信息。BaseValve:基础Valve,与Piple中Value的方法相同:invoke(Requestrequest,Responseresponse),主要作用是连接父子容器,将父容器的请求传递给子容器.假设Servlet是处理最终请求的实体,那么请求可以想象成一条从Engine到Host再到Context,最后到Wrapper的传输链。这条链是通过Pipeline连接起来的,链中的处理节点就是valveValue,每条链之间的joint就是基本的valveBaseValue。如图11所示,connector收到浏览器的请求后,将请求转发给容器中的Engine。图11:Tomcat容器之间的信息传递Engine通过Pipeline传递信息,其中Value是valve,可以通过invoke方法处理请求信息。信息在Pipeline中通过valve传递后,会通过基础valve即BaseValue传递给下一个容器组件Host,Host再通过Context逐层传递信息给Wrapper中的Wrapper同样的方式。①EngineTomcat中的connector收到消息并解析后,将消息传递给Engine容器。用户可以在Engine容器的Pipeline中添加各种自定义的Valves,Engine容器会一一调用Pipeline中的Valves。Engine容器的BaseValve是StandardEngineValve。这个Valve会读取Request中的Host信息,然后将请求路由到对应的Host容器中。②HostHost是Engine的一个子容器,每个Host容器都是对应不同域名的虚拟主机。从1.1开始,HTTP协议支持在请求头中增加一个Host字段来表示请求的域名。Host就是通过解析这个域名来判断并向不同的Host发送请求。DNS域名解析时,不同的域名可以解析到同一个IP或主机。Engine容器的BaseValve会读取Request中的Host,然后调用Host容器对应的Pipeline来处理消息。假设Tomcat支持三个域名:http://www.a.comhttp://www.b.comhttp://www.c.com在Tomcat配置文件服务器的Engine标签下添加多个域名。xml主机选项卡。如图12所示,在配置中的Host节点中,name代表域名,appbase代表虚拟主机的目录。图12:主机配置当我们在浏览器中输入http://www.a.com时,Tomcat通过读取server.xml中的配置信息找到www.a.com对应的虚拟主机Host,然后使用搜索来处理请求的主机。③Context表示运行在虚拟主机上的Web应用。每个Web应用都是基于一个WAR文件,或者说是解压后的WAR文件对应的目录(这里称为应用目录)。Context是Host的子容器,每个Host可以定义任意数量的Context元素。④Wrapper是最小的容器,每个Wrapper对应一个Servlet实例。当请求转发给Wrapper容器时,Wrapper容器调用Pipeline方法后,会使用特定的类加载器加载Servlet类,实例化并初始化Servlet,然后将请求传递给Servlet的service方法进行处理.上面已经为大家一一介绍了Tomcat容器的四个组成部分,这里通过一个例子带大家完成请求容器的过程。图十三:Tomcat容器的执行流程如图十三:假设浏览器向Tomcat连接器发送请求,向www.a.com网站发送GET请求,请求内容为/AppA/ServletA。连接器根据指定的协议和IO方法处理请求的Socket报文,将Socket解析为对应的Request实体,传递给容器中的Engine。connector向Engine容器发送请求,Engine容器存储请求域名与Host容器的映射关系。找到“www.a.com”域名对应的Host容器,向对应的Host发送请求。Host容器继续解析请求中的路径。如果配置了路径和应用的关系,比如“/AppA”对应的Context容器,Host容器会安装配置发送请求到对应应用的Context容器。Host容器解析路径后,将应用交给Context容器,不同的请求可以通过路径映射到不同的Servlet容器。比如图中的“/ServletA”对应Wrapper容器,Context容器将请求传递给Wrapper容器。然后Wrapper容器会加载对应的ServletA实现类,调用servlet实现类中的逻辑处理Request,并将处理结果写入Response。小结本文主要围绕Tomcat的架构,向大家介绍一下。首先,通过Web应用提供的静态和动态资源引入ServletWeb应用。因此,HTTP服务器和Servlet容器的功能构成了Web应用,这就是Tomcat的实现原理。然后介绍了Tomcat的两个重要组件:连接器和容器。连接器处理IO模型和传输协议并请求适配。容器包括Engine、Host、Context、Wrapper,并描述了它们的父子关系和各个组件的功能。举例说明容器组件之间请求传递的过程。作者:崔浩简介:十六年开发架构经验。曾在惠普武汉交付中心担任技术专家、需求分析师、项目经理,后在一家初创公司担任技术/产品经理。善于学习,乐于分享。目前专注于技术架构和研发管理。编辑:陶佳龙征稿:如有意向投稿或寻求报道,请联系editor@51cto.com【原创稿件请注明原作者和出处为.com,合作网站转载】
