如今,随着互联网技术的快速发展,各种各样的技术不同 Web 应用层出不穷,Tomcat 作为 Web 携带应用容器 Web 请求处理和响应工作。
大多数企业 Web 所有的应用程序都在其上运行,Tomcat 对程序员来说,这是一个老朋友,所以今天带你去接近这个老朋友,看看它是如何处理的 Web 请求及其内部系统结构有助于我们理解 Tomcat 使用有很大的好处。
您将学习以下内容:
- Web 容器与 Tomcat
- Tomcat 连接器
- Tomcat 容器
要说清楚 Tomcat 首先要从早期做的事情开始 Web 应用说起。
如图 1 如图所示,用户首先通过浏览器查看静态资源,如新闻,此时需要通过 HTTP 服务器将静态返回浏览器 HTML 浏览器将分析资源和资源 HTML 呈现给用户。
这里的 Web 容器是用来存放的 HTTP 能够处理网络请求并响应的服务器。
随着互联网的发展,用户需求从静态资源转向动态资源获取,浏览器在获取资源时与服务器互动。
由此 Web 除了能够处理外,容器的功能开始扩展 HTTP 要求,还需要 HTTP 服务器调用服务端程序通常被称为 Web 应用。
针对这一需求,Sun 公司推出了 Servlet 技术, Servlet 在服务端运行 Java 小程序。
由于 Servlet 它不能独立运行,所以需要一个 Servlet 容器承载,并进行初始化启动等管理操作。
如图 3 所示,为了满足用户日益增长的需求 Web 添加到容器中 Servlet 作为 Web 应用程序为用户提供动态资源。
为了承载 Servlet,也加入了 Servlet 只有每一个容器 Servlet 它们都代表一个业务类别,包括一些业务应用程序,如果它们被访问 Web 如果用户在容器中提供统一的服务响应,则需要遵循统一的接口。
说白了就是要遵守一定的规则才能放在上面 Servlet 在容器中,管理方便,所以这个规则是 Servlet 接口。从图中可以看出。 Servlet 接口对单个接口 Servlet 标准定义。
如图 4 所示,对于 Servlet 界面是定义的 init 方法用做 Servlet 同时,资源的初始化也被定义 destroy 方法用做 Servlet 释放资源。
其中 Service 该方法用于实现具体的业务需求,可以看到该方法的引入 ServletRequest 和 ServletResponse 两个参数分别包装了用户的请求信息和 Servlet 响应信息。
我们以后再介绍一下 Spring MVC 在 Tomcat 中间运行时也是如此 Servlet 存在的方式,由 DispatcherServlet 在 init 方法里创建 Spring MVC 容器。
接口中的 getServletConfig 方法会返回 ServletConfig,ServletConfig 是用来封装 Servlet 初始化参数可以在 web.xml 配置 Servlet 参数,然后通过 getServletConfig 获取参数的方法。
上面介绍了 Servlet 接口,然后通过图片 5 对 Servlet 深入了解接口调用的周边类。
如图 5 所示,Servlet 接口依赖 ServletConfig 接口只是用来处理的 Servlet 配置参数,ServletConfig 界面也会同时关联 ServletContext 获取 Servlet 上下文的信息。
Servlet 接口中的 service 方法分别依赖两个参数 ServletRequest 和 ServletResponse。
同时还有两个接口 HttpServletRequest 和 HttpServletResponse 会分别继承 ServletRequest 和 ServletResponse。
一般而言 Servlet 作为一个接口,实现这个接口需要具体的实现类,所以 Servlet 规范提供了一个抽象的名称 GenericServlet,它实现了 Servlet。
接着有一个 HttpServlet 的类继承 GenericServlet,为了处理 HTTP 这种请求也会依赖 HttpServletRequest 和 HttpServletResponse。
Servlet 接口定义是 Servlet 容器的重要组成部分,Servlet 容器通过接口管理接入 Servlet 实体。
接下来看看 Servlet 这里将根据工作模式对容器进行分类 Servlet 容器分为 3 类:
①独立运行的 Servlet 容器这种模式下,Servlet 容器作为组件 Web 服务器的一部分。使用时 Java 的 Web 这种模式在服务器中也会使用,Tomcat 如果不是基于默认模式, Java 的 Web 服务需要使用以下两种模式。
②内置的 Servlet 容器Servlet 容器由 Web 服务器插件和 Java 容器由两部分组成。需要 Web 打开服务器内部地址空间 JVM,在此 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 所示,这里通过 8 一步一步地展示 HTTP 请求及响应流程:
- 用户通过浏览器启动 HTTP 请求。
- Servlet 接到请求后,容器将对其进行处理 HTTP 请求进行分析。
- 通过分析结果和配置信息创建 Servlet 实例。
- 例子创建后调用 Servlet 实例的 init 实例初始化工作的方法完成。
- 接下来是调用 Servlet 中的 Service 完成具体业务的方法。
- Service 方法完成后,响应信息将返回 Servlet 容器。
- Servlet 容器将 Servlet 创建返回信息 HTTP 回复浏览器端的响应。
- 最后,Servlet 容器调用 destroy 方法卸载掉 Servlet,并释放相应的资源。
写在这里给大家做一个小节,Web 容器是用来提供的 Web 当用户要求服务器时 Web 当容器通过时 HTTP 服务器对 HTTP 请求进行分析,让解析后的请求交给 Servlet 容器。
Servlet 容器负责定义 Servlet 接口规范和管理 Servlet 程序,一切 Web 应用都会以 Servlet 每一种形式都存在 Servlet 都需要遵循 Servlet 根据界面的定义 Servlet 响应用户要求的处理流程。
Tomcat 这就是我们所说的 Web 容器,上述原理和处理过程是 Tomcat 的工作。
简单点说 Tomcat=HTTP 服务器+Servlet 容器(Servlet 只要服务提供服务,接口规范) Servlet 接口的 Servlet 都可以在 Tomcat 上述操作,并对外提供服务。
Tomcat 连接器介绍了上一节 Tomcat 设计思路,作为 Web 容器的 Tomcat 实现了 HTTP 服务器和 Servlet 容器的功能,
所以可以概括为两个功能:
- 第一处理 Socket 连接,负责分析相应的网络请求 Request 和 Response 对象。
- 二是加载和管理 Servlet,并且处理 Request 返回 Response。
因此,引出 Tomcat 两个核心组件:连接器(Connector)和容器(Container),连接器负责外部通信,容器负责内部通信 Servlet 的管理。
如图 7 所以,当浏览器有的时候 Request 到 Tomcat 当连接器对其进行分析并产生相应的 ServletRequest 并将其传送到容器进行处理。
容器处理完毕后,将返回连接器 ServletResponse,同样,连接器也会接通 ServletResponse 解析成 Response 并返回浏览器。
在介绍了连接器和容器之前的关系后,让我们关注连接器支持的三个应用层协议:
- HTTP/1.1协议:这是绝大多数 Web 访问协议主要用于应用 Tomcat 单独运行(不 Web 服务器集成)。
- AJP 协议:用于和 Web 服务器(如 Apache HTTP Server)为实现静态资源优化和集群部署,目前支持 AJP/1.3。
- HTTP/2.0 协议:下一代 HTTP 协议,自 Tomcat8.5 以及 9.0 版本开始支持,到目前为止,最新的主流版本已经支持了 HTTP/2.0。
上面了解了 Tomcat 接收网络请求协议后,介绍接收网络请求后连接器的动作。
Tomcat 连接器将通过以下组件处理网络请求:
①Endpoint:作为连接器的通信端点,负责监控通信端口,实现了 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 根据协议的不同提供 3 个实现类:
- Http11Processorrssorr(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 接口。
每个应用层协议都有自己的抽象基类,例如 AbstractAjpProtocol 和 AbstractHttp11Protocol。
④Adapter:负责转换请求,将 Tomcat Request 对象转化成 ServletRequest 对象。
由于 Tomcat 可加载任意符合 Servlet 接口规范的 Servlet 例子,所以需要使用 ServletRequest 对象与之通信,因此需要使用该组件来完成要求对象的适应。
前面介绍了 Tomcat 连接器的几个组件,在这里整理连接器处理请求的流程,如图所示 9 所示。
用户通过浏览器对 Tomcat 发起请求,连接器通过 Endpoint 通过接收请求 I/O 模型处理后,将结果交给结果 Processor。
Processor 处理应用层协议,然后将请求交给我们。 Adapter,Adapter 适配后的 ServletRequest 送到容器处理。
Tomcat 容器正如我们上面所说 Tomcat 承载处理网络的连接器 IO 请求和协议处理工作,并将请求适应发送给容器。
接下来我们来看看 Tomcat 首先,看看容器的组成,如图10所示 所示:
Tomcat 一个将包含在容器中 Engine 容器,一个 Engine 它可以包含多个容器 Host 容器,每个 Host 它可以包含多个容器 Context 也就是说,容器 Host 多个应用程序可以包含在下面,每个应用程序对应一个 Context 容器。
每个 Context 可包含多个容器作为应用程序 Wrapper 容器:每个 Wrapper 包含一个容器 Servlet 也就是说,容器 Tomcat 允许多个应用程序 Servlet 实现。
介绍完 Tomcat 我们知道它是由容器组成的 Engine、Host、Context 和 Wrapper 构图,并且知道它们之间的包含关系,接下来就来看看每个组件需要完成的工作。
Tomcat 四种容器结构相同,包括:
- Pipeline:用于处理请求中的每个信息 Pipeline 包括多个阀门 Valve,每个 Valve 都有同样的方法 invoke(Request request,Response response),请求中的信息可以在此方法中处理。
- BaseValve:基础阀,它和 Piple 中的阀门 Value 方法相同:invoke(Request request,Response response),它的主要功能是连接父子容器,将要求从父子容器传输到子容器。
假设 Servlet 如果是处理最终请求的实体,请求从 Engine 到 Host 再到 Context,最终达到 Wrapper 它可以想象成一条传输链。
这个链条是通过的 Pipeline 链条中的处理节点是阀门 Value,每个链之间的接头是基本阀 BaseValue。
如图 11 如所示,接收到浏览器的请求后,连接器将请求转发给容器 Engine。
Engine 通过中将信息 Pipeline 传递,其中 Value 是阀门,它可以通过 invoke 该方法处理请求信息。
信息通过阀门 Pipeline 传输后,它将通过基本阀门,即 BaseValue 传输到下一个容器组件 Host 中去,Host 通过如法炮制,将信息一层一层地通过 Context 传递到 Wrapper 中。
①EngineTomcat 中间的连接器接收并分析消息后,会将消息转移给 Engine 用户可以给容器 Engine 容器的 Pipeline 添加各种自定义 Valve,Engine 容器将逐一调用 Pipeline 中的 Valve。
Engine 容器的 BaseValve 是 StandardEngineValve,这个 Valve 会读取 Request 中的 Host 信息,然后将要求路由交给相应的信息 Host 容器。
②HostHost 是 Engine 子容器,每一个 Host 容器是对应不同域名的虚拟主机。
HTTP 协议从 1.1 一开始,支持在请求头添加 Host 用于表示要求的域名的字段。
Host 通过分析域名来判断将请求发送到不同的地方 Host。DNS 当域名分析时,可以将不同的域名分析到同一个域 IP 或者主机。
Engine 容器的 BaseValve 会读取 Request 中的 Host,然后调用相应的 Host 容器的 Pipeline 处理消息。
假设 Tomcat 支持三个域名:
- http://www.a.com
- http://www.b.com
- http://www.c.com
在 Tomcat 的配置文件 server.xml 中的 Engine 在标签下添加多个标签 Host 标签。
如图 12 所示,配置中的 Host 节点中 name 表示域名,appbase 表示虚拟主机目录。
当我们输入浏览器时 http://www.a.com 时,Tomcat 通过读取 server.xml 找到中间的配置信息 www.a.com 相应的虚拟主机 Host,然后用找到的 Host 处理请求。
③Context一个代表在虚拟主机上运行的代表 Web 应用。每个 Web 应用基于 WAR 文件,或 WAR 文件解压后对应的目录(以下简称应用目录)Context 是 Host 子容器,每一个 Host 可以定义任意多的 Context 元素。
④Wrapper它是最小的容器,每个容器 Wrapper 对应一个 Servlet 实例。
转发请求时 Wrapper 容器之后,Wrapper 容器在调用 Pipeline 方法结束后,将使用特定的类加载器加载 Servlet 类,对 Servlet 实例化和初始化,然后提交请求 Servlet 的 service 处理方法。
上面把 Tomcat 一个接一个地介绍了容器的四个组件。在这里,我们将通过一个例子带您再次访问请求容器的过程。
如图 13 所示:
- 假设浏览器是对的 Tomcat 发送连接器的请求 www.a.com 网站发送 GET 请求,请求内容是 /AppA/ServletA。
- 根据指定的协议和协议,连接器 IO 处理请求的方式 Socket 消息,解析 Socket 为对应的 Request 实体,并将其传输到容器中 Engine。
- 连接器将要求交付 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 应用程序提供的静态和动态资源的切入导致 Servlet 的 Web 应用。
因此 HTTP 服务器和 Servlet 它由容器的功能组成 Web 这就是应用 Tomcat 实现原理。
然后介绍 Tomcat 两个重要组件:连接器和容器。连接器处理 IO 并对模型和传输协议进行了请求适应。
容器包含了 Engine、Host、Context、Wrapper,并通过一个例子描述了它们的父子关系和每个组件的功能,描述了容器组件之间的请求传递过程。