NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

Tomcat 处理 HTTP 请求源码分析(上)

  • 2011-12-08
  • 本文字数:4807 字

    阅读完需:约 16 分钟

很多开源应用服务器都是集成 tomcat 作为 web container 的,而且对于 tomcat 的 servlet container 这部分代码很少改动。这样,这些应用服务器的性能基本上就取决于 Tomcat 处理 HTTP 请求的 connector 模块的性能。本文首先从应用层次分析了 tomcat 所有的 connector 种类及用法,接着从架构上分析了 connector 模块在整个 tomcat 中所处的位置,最后对 connector 做了详细的源代码分析。并且我们以 Http11NioProtocol 为例详细说明了 tomcat 是如何通过实现 ProtocolHandler 接口而构建 connector 的。

通过本文的学习,应该可以轻松做到将 tomcat 做为 web container 集成到第三方系统,并且自定义任何你想要的高性能的 HTTP 连接器。

1 Connector 介绍

1.1 Connector 的种类

Tomcat 源码中与 connector 相关的类位于 org.apache.coyote 包中,Connector 分为以下几类:

  • Http Connector, 基于 HTTP 协议,负责建立 HTTP 连接。它又分为 BIO Http Connector 与 NIO Http Connector 两种,后者提供非阻塞 IO 与长连接 Comet 支持。
  • AJP Connector, 基于 AJP 协议,AJP 是专门设计用来为 tomcat 与 http 服务器之间通信专门定制的协议,能提供较高的通信速度和效率。如与 Apache 服务器集成时,采用这个协议。
  • APR HTTP Connector, 用 C 实现,通过 JNI 调用的。主要提升对静态资源(如 HTML、图片、CSS、JS 等)的访问性能。现在这个库已独立出来可用在任何项目中。Tomcat 在配置 APR 之后性能非常强劲。

1.2 Connector 的配置

对 Connector 的配置位于 conf/server.xml 文件中。

1.2.1 BIO HTTP/1.1 Connector 配置

一个典型的配置如下:

复制代码
<Connector port=”8080” protocol=”<b>HTTP/1.1</b>” maxThreads=”150”
connectionTimeout=”20000” redirectPort=”8443”

其它一些重要属性如下:

  • acceptCount : 接受连接 request 的最大连接数目,默认值是 10
  • address : 绑定 IP 地址,如果不绑定,默认将绑定任何 IP 地址
  • allowTrace : 如果是 true, 将允许 TRACE HTTP 方法
  • compressibleMimeTypes : 各个 mimeType, 以逗号分隔,如 text/html,text/xml
  • compression : 如果带宽有限的话,可以用 GZIP 压缩
  • connectionTimeout : 超时时间,默认为 60000ms (60s)
  • maxKeepAliveRequest : 默认值是 100
  • maxThreads : 处理请求的 Connector 的线程数目,默认值为 200

如果是 SSL 配置,如下:

复制代码
<Connector port="8181" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol = "TLS"
address="0.0.0.0"
<b>keystoreFile="E:/java/jonas-full-5.1.0-RC3/conf/keystore.jks"</b>
<b>keystorePass="changeit" </b>/>

其中,keystoreFile 为证书位置,keystorePass 为证书密码

1.2.2 NIO HTTP/1.1 Connector 配置

复制代码
<Connector port=”8080” protocol=”<b>org.apache.coyote.http11.Http11NioProtocol</b>”
maxThreads=”150” connectionTimeout=”20000” redirectPort=”8443”

1.2.3 Native APR Connector 配置

  1. ARP 是用 C/C++ 写的,对静态资源(HTML,图片等)进行了优化。所以要下载本地库 tcnative-1.dll 与 openssl.exe,将其放在 %tomcat%\bin 目录下。

下载地址是: http://tomcat.heanet.ie/native/1.1.10/binaries/win32/
2. 在 server.xml 中要配置一个 Listener, 如下图。这个配置 tomcat 是默认配好的。 ```

``` 3. 配置使用 APR connector ```

<Connector port=”8080” protocol=”org.apache.coyote.http11.Http11AprProtocol

maxThreads=”150” connectionTimeout=”20000” redirectPort=”8443”

``` 4. 如果配置成功,启动 tomcat, 会看到如下信息: ```

org.apache.coyote.http11.Http11AprProtocol init

复制代码
## 2 Connector 在 Tomcat 中所处的位置
### 2.1 Tomcat 架构
![](https://static001.infoq.cn/resource/image/59/05/59a324e70eab7cf85ca1ea7173a6e005.jpg)
** 图 2-1 Tomcat 架构 **
- Server(服务器) 是 Tomcat 构成的顶级构成元素,所有一切均包含在 Server 中,Server 的实现类 StandardServer 可以包含一个到多个 Services;
- 次顶级元素 Service 的实现类为 StandardService 调用了容器 (Container) 接口,其实是调用了 Servlet Engine(引擎),而且 StandardService 类中也指明了该 Service 归属的 Server;
- 接下来次级的构成元素就是容器 (Container),主机 (Host)、上下文 (Context) 和引擎 (Engine) 均继承自 Container 接口,所以它们都是容器。但是,它们是有父子关系的,在主机 (Host)、上下文 (Context) 和引擎 (Engine) 这三类容器中,引擎是顶级容器,直接包含是主机容器,而主机容器又包含上下文容器,所以引擎、主机和上下文从大小上来说又构成父子关系,虽然它们都继承自 Container 接口。
- 连接器 (Connector) 将 Service 和 Container 连接起来,首先它需要注册到一个 Service,它的作用就是把来自客户端的请求转发到 Container(容器),这就是它为什么称作连接器的原因。
故我们从功能的角度将 Tomcat 源代码分成 5 个子模块,它们分别是:
1. Jsper 子模块:这个子模块负责 jsp 页面的解析、jsp 属性的验证,同时也负责将 jsp 页面动态转换为 java 代码并编译成 class 文件。在 Tomcat 源代码中,凡是属于 org.apache.jasper 包及其子包中的源代码都属于这个子模块;
2. Servlet 和 Jsp 规范的实现模块:这个子模块的源代码属于 javax.servlet 包及其子包,如我们非常熟悉的 javax.servlet.Servlet 接口、javax.servet.http.HttpServlet 类及 javax.servlet.jsp.HttpJspPage 就位于这个子模块中;
3. Catalina 子模块:这个子模块包含了所有以 org.apache.catalina 开头的 java 源代码。该子模块的任务是规范了 Tomcat 的总体架构,定义了 Server、Service、Host、Connector、Context、Session 及 Cluster 等关键组件及这些组件的实现,这个子模块大量运用了 Composite 设计模式。同时也规范了 Catalina 的启动及停止等事件的执行流程。从代码阅读的角度看,这个子模块应该是我们阅读和学习的重点。
4. Connectors 子模块:如果说上面三个子模块实现了 Tomcat 应用服务器的话,那么这个子模块就是 Web 服务器的实现。所谓连接器 (Connector) 就是一个连接客户和应用服务器的桥梁,它接收用户的请求,并把用户请求包装成标准的 Http 请求 (包含协议名称,请求头 Head,请求方法是 Get 还是 Post 等等)。同时,这个子模块还按照标准的 Http 协议,负责给客户端发送响应页面,比如在请求页面未发现时,connector 就会给客户端浏览器发送标准的 Http 404 错误响应页面。
5. Resource 子模块:这个子模块包含一些资源文件,如 Server.xml 及 Web.xml 配置文件。严格说来,这个子模块不包含 java 源代码,但是它还是 Tomcat 编译运行所必需的。
### 2.2 Tomcat 运行流程
![](https://static001.infoq.cn/resource/image/f1/d4/f1aa8a781479109792f43efbc6793fd4.jpg)
** 图 2-2 tomcat 运行流程 **
假设来自客户的请求为:<http://localhost:8080/test/index.jsp>
1. 请求被发送到本机端口 8080,被在那里侦听的 Coyote HTTP/1.1 Connector 获得
2. Connector 把该请求交给它所在的 Service 的 Engine 来处理,并等待 Engine 的回应
3. Engine 获得请求 localhost:8080/test/index.jsp,匹配它所有虚拟主机 Host
4. Engine 匹配到名为 localhost 的 Host(即使匹配不到也把请求交给该 Host 处理,因为该 Host 被定义为该 Engine 的默认主机)
5. localhost Host 获得请求 /test/index.jsp,匹配它所拥有的所有 Context
6. Host 匹配到路径为 /test 的 Context(如果匹配不到就把该请求交给路径名为 "" 的 Context 去处理)
7. path="/test" 的 Context 获得请求 /index.jsp,在它的 mapping table 中寻找对应的 servlet
8. Context 匹配到 URL PATTERN 为\*.jsp 的 servlet,对应于 JspServlet 类
9. 构造 HttpServletRequest 对象和 HttpServletResponse 对象,作为参数调用 JspServlet 的 doGet 或 doPost 方法
10. Context 把执行完了之后的 HttpServletResponse 对象返回给 Host
11. Host 把 HttpServletResponse 对象返回给 Engine
12. Engine 把 HttpServletResponse 对象返回给 Connector
13. Connector 把 HttpServletResponse 对象返回给客户 browser
## 3 Connector 源码分析
### 3.1 Tomcat 的启动分析与集成设想
我们知道,启动 tomcat 有两种方式:
- 双击 bin/startup.bat
- 运行 bin/catalina.bat run
它们对应于 Bootstrap 与 Catalina 两个类,我们现在只关心 Catalina 这个类,这个类使用 Apache Digester 解析 conf/server.xml 文件生成 tomcat 组件,然后再调用 Embedded 类的 start 方法启动 tomcat。
所以,集成 Tomcat 的方式就有以下两种了:
- 沿用 tomcat 自身的 server.xml
- 自己定义一个 xml 格式来配置 tocmat 的各参数,自己再写解析这段 xml,然后使用 tomcat 提供的 API 根据这些 xml 来生成 Tomcat 组件,最后调用 Embedded 类的 start 方法启动 tomcat
个人觉得第一种方式要优越,给开发者比较好的用户体验,如果使用这种,直接模仿 Catalina 类的方法即可实现集成。
目前,JOnAS 就使用了这种集成方式,JBoss、GlassFish 使用的第二种自定义 XML 的方式。
### 3.2 Connector 类图与顺序图
![](https://static001.infoq.cn/resource/image/96/6e/96a9a2835ef2e2ad0d7ed0bda9643b6e.jpg)
** 图 3-1 Connector 相关类图 **
![](https://static001.infoq.cn/resource/image/e0/46/e0ae1bbf830029d7987ec352097aa746.jpg)
** 图 3-2 Connector 工作流程顺序图 **
从上面二图中我们可以得到如下信息:
1. Tomcat 中有四种容器 (Context、Engine、Host、Wrapper),前三者常见,第四个不常见但它也是实现了 Container 接口的容器
2. 如果要 ** 自定义一个 Connector 的话,只需要实现 ProtocolHander 接口 **, 该接口定义如下:
![](https://static001.infoq.cn/resource/image/d6/41/d68e087d447f8aa1430fb43d80733b41.jpg)
** 图 3-3 自定义 connector 时需实现的 ProtocolHandler 接口 **
Tomcat 以 HTTP(包括 BIO 与 NIO)、AJP、APR、内存四种协议实现了该接口(它们分别是:AjpAprProtocol、AjpProtocol、Http11AprProtocol、Http11NioProtocol、Http11Protocal、JkCoyoteHandler、MemoryProtocolHandler),要使用哪种 Connector 就在 conf/server.xml 中配置,在 Connector 的构造函数中会通过反射实例化所配置的实现类:

复制代码
### 3.3 Connector 的工作流程
下面我们以 Http11AprProtocol 为例说明 Connector 的工作流程。
1. 它将工作委托给 NioEndpoint 类。在 NioEndpoint 类的 init 方法中构建一个 SocketServer(当然,不同的实现类会有一些微小的变化,例如如果是 NIO,它构建的就是 SocketServerChannel)
2. 在 NioEndpoint.Acceptor 类中会接收一个客户端新的连接请求,如下图: ![](https://static001.infoq.cn/resource/image/60/c2/60d40698ebc98c35b716482674940dc2.jpg)
3. 在 NioEndpoint 类中,有一个内部接口 Handle,该接口定义如下: ![](https://static001.infoq.cn/resource/image/f1/86/f146a6241cd6ba3891202b856f3c8186.jpg)
4. 在 Http11NioProtocol 类中实现了 Handle 这个内部接口,并调用 Http11NioProcessor 类 (该类实现了 ActionHook 回调接口)。在 Response 类中会调用 ActionHook 实现类的相关方法的,Response 类的 action 方法如下: ![](https://static001.infoq.cn/resource/image/2a/12/2a01ddee8e5102e131b20890f706c012.jpg)
5. Http11NioProcessor 的 process 实现方法中,会通过 Adapter 来调用 Servler 容器生成响应结果。
## 关于作者
张华,长期从事 Java 方面的开发工作,有搜索引擎、中间件应用服务器、互联网、云计算等领域的行业经验,目前正在从事基于 Power 的虚拟化技术研发。博客地址:<http://blog.csdn.net/quqi99>
- - - - - -
感谢 [张凯峰](http://www.infoq.com/cn/bycategory.action?authorName=%E5%BC%A0%E5%87%AF%E5%B3%B0) 对本文的审校。
给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 [editors@cn.infoq.com](mailto:editors@cn.infoq.com)。也欢迎大家加入到 [InfoQ 中文站用户讨论组](http://groups.google.com/group/InfoQChina) 中与我们的编辑和其他读者朋友交流。
2011-12-08 00:0052994

评论

发布
暂无评论
发现更多内容

跟我读论文丨ACL2021 NER 模块化交互网络用于命名实体识别

华为云开发者联盟

自然语言处理 机器学习 ACL2021 NER 模块化交互网络 实体识别

Go 学习笔记之 反射

架构精进之路

Go 语言 8月日更

手撸二叉树之二叉树的最近公共祖先

HelloWorld杰少

数据结构与算法 8月日更

【LeetCode】二叉树的镜像Java题解

Albert

算法 LeetCode 8月日更

2021年8月国产数据库排行榜:TiDB稳榜首,达梦返前三,Kingbase进十强,各厂商加速布局云生态

墨天轮

数据库 opengauss TiDB oceanbase 国产数据库

【Vue2.x 源码学习】第三十四篇 - 组件部分-Vue组件与初始化流程简介

Brave

源码 vue2 8月日更

Apache Pulsar 里程碑简史:打造统一消息流平台与生态

Apache Pulsar

Apache Pulsar StreamNative

“互联网+”大赛之智慧校园赛题攻略:你的智慧校园,WeLink帮你来建

华为云开发者联盟

小程序 华为云 welink 智慧校园 “互联网+”大赛

源码级深挖AQS队列同步器

码农参上

AQS 锁机制 8月日更

Spring @Transactional 注解事务

Rubble

springboot 8月日更

云小课 | 网络知识一箩筐——NAT网关,让IP地址华丽变身,轻松实现内外网互通

华为云开发者联盟

私网NAT网关 NAT网关 公网NAT网关

FastApi-10-Example

Python研究所

FastApi 8月日更

Debian 10 安装 phpMyAdmin

Tao

MySQL 服务器 PHP-FPM MariaDB Debian

B轮融资逾2亿高瓴创投领投,最懂金融的RPA厂商金智维有何不凡之处?

王吉伟频道

RPA 金融科技 机器人流程自动化 做市机器人 金智维

Vue进阶(二十八):浅析 Vue 中 computed 与 method 区别

No Silver Bullet

Vue 8月日更 computed

Compose 中的 ConstraintLayout

Changing Lin

8月日更

主打年轻群体,2022款欧拉黑/白猫6.98万元起正式预售!

科技热闻

Java Array 和 String 的转换

HoneyMoose

MySQL 系列教程之(一)初识 MySQL

若尘

MySQL 8月日更

从0开始的TypeScriptの六:webpack5热更新打包TS

空城机

JavaScript typescript 大前端 8月日更

大厂offer?拿来吧你!网易有道笔试编程题特辑

有道技术团队

招聘 笔试 #技术干货# 网易有道

3 条掏心掏肺的建议,新手学习编程必备,快上车!

沉默王二

编程

学习笔记:HTTP消息的响应码

姬翔

netty系列之:文本聊天室

程序那些事

Java Netty nio 程序那些事

LeetCode题解:783. 二叉搜索树节点最小距离,递归,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

「免费开源」基于Vue和Quasar的前端SPA项目crudapi后台管理系统实战之EXCEL数据导出(十三)

crudapi

Vue crud crudapi qusar 数据导出

4种基于像素分割的文本检测算法

华为云开发者联盟

目标检测算法 文本检测 像素分割 文本检测算法 文本

Go协程并发之百万级并发「让我们一起Golang」

Regan Yue

高并发 协程 Go 语言 8月日更

API纠错+翻译,就等您大展身手!

Geek_6cdeb6

解决「停车难」,EMQ 映云科技数据接入方案在智慧停车平台中的应用

EMQ映云科技

大数据 物联网 移动互联网 智慧交通 emq

如何评价《Java 并发编程艺术》这本书?

cxuan

书籍推荐 java 并发

Tomcat处理HTTP请求源码分析(上)_Java_张华_InfoQ精选文章