本文档是当前 Apache JServ 协议版本 1.3(也称为 ajp13)的演进提案。在此我不会涵盖完整的协议,只介绍 ajp13 的附加功能。这第 N 次修订包含了来自 tomcat-dev 邮件列表的评论以及开发过程中发现的遗漏。
AJP13 中缺失的功能
ajp13 是一个很好的协议,用于将 Tomcat 这样的 Servlet 引擎连接到 Apache 这样的 Web 服务器
- 使用持久连接以避免每次请求时的重新连接时间
- 编码许多 HTTP 命令以减小流大小
- 向 Servlet 引擎发送来自 Web 服务器的许多信息(例如 SSL 证书)
但 ajp13 缺乏对以下方面的支持:
- Web 服务器和 Servlet 引擎之间的安全性。任何人都可以连接到 ajp13 端口(没有使用登录机制)。例如,您可以使用 telnet 连接,并且不发送任何数据(连接中没有超时)来保持远程线程的活跃。
- 从 Servlet 引擎传递到 Web 服务器的上下文信息。JK(Web 服务器连接器)的部分配置是向 Web 服务器指示要处理哪些 URI。mod_jk 的 JkMount 指令告知 Web 服务器哪些 URI 必须转发到 Servlet 引擎。Servlet 引擎已经知道它处理哪些 URI,并且 TC 3.3 已经能够从可用上下文列表中为 JK 生成配置文件。
- 从 Servlet 引擎到 Web 服务器的上下文状态更新。拥有大量 Tomcat 集群的大型站点,如 ISP 和虚拟主机提供商,可能需要出于管理目的停止某个上下文。在这种情况下,前端 Web 服务器必须知道该上下文当前已关闭,以便最终将请求转发到另一个 Tomcat。
- 在发送请求之前验证连接状态。实际上,JK 将请求发送到 Servlet 引擎,然后等待响应。但 Socket API 的一个优点是,您可以对已关闭的连接执行 write() 操作而不会报告任何错误,但对已关闭的连接执行 read() 操作会返回错误代码。
AJP13 的拟议附加功能
在此,我们将描述可以添加到 AJP13 的功能和附加组件。由于本文档是一个提案,最初可能会有一些混乱,这是可以预料的。请确保在 Tomcat 邮件列表上的讨论将有助于澄清观点,添加功能,但当前的列表似乎是“最低限度必要”的。
- 连接时的先进登录功能
- 基本授权系统,其中 Web 服务器和 Servlet 引擎中存在共享密钥。
- 基本协议协商,仅为确保将来 AJP13 添加功能时,现有实现仍能正常工作。
- “未知数据包”的干净处理
- 从 Web 服务器传递到 Servlet 引擎的扩展环境变量。
- 添加 Servlet 2.3 API 所需的额外 SSL 信息(如 SSL_KEY_SIZE)
高级登录
- WEB-SERVER 发送 LOGIN INIT CMD + NEGOCIATION DATA + WEB SERVER INFO
- TOMCAT 回复 LOGIN SEED CMD + RANDOM DATA
- WEB-SERVER 计算 RANDOM DATA+SECRET DATA 的 MD5
- WEB-SERVER 发送 LOGIN COMP CMD + MD5 (SECRET DATA + RANDOM DATA)
- TOMCAT 回复 LOGIN STATUS CMD + NEGOCIED DATA + SERVLET ENGINE INFO
消息流
+----------------+------------------+-----------------+
| LOGIN INIT CMD | NEGOCIATION DATA | WEB SERVER INFO |
+----------------+------------------+-----------------+
+----------------+----------------+
| LOGIN SEED CMD | MD5 of entropy |
+----------------+----------------+
+----------------+----------------------------+
| LOGIN COMP CMD | MD5 of RANDOM + SECRET KEY |
+----------------+----------------------------+
+-----------+---------------+---------------------+
| LOGOK CMD | NEGOCIED DATA | SERVLET ENGINE INFO |
+-----------+---------------+---------------------+
+------------+--------------+
| LOGNOK CMD | FAILURE CODE |
+------------+--------------+
- LOGIN INIT CMD, LOGIN SEED CMD, LOGIN COMP CMD, LOGOK CMD, LOGNOK CMD 均为 1 字节长。
- MD5,RANDOM + SECRET KEY 的 MD5 均为 32 字符长。
- NEGOCIATION DATA, NEGOCIED DATA, FAILURE CODE 均为 32 位长。
- WEB SERVER INFO, SERVLET ENGINE INFO 均为 CString。
worker.ajp13.port=8009
worker.ajp13.host=localhost
worker.ajp13.type=ajp13
worker.ajp13.secretkey=myverysecretkey
关机功能
AJP13 缺少 AJP12 的一项功能,即关机命令。注销将告知 Servlet 引擎自行关闭。
+--------------+----------------------------+
| SHUTDOWN CMD | MD5 of RANDOM + SECRET KEY |
+--------------+----------------------------+
+------------+
| SHUTOK CMD |
+------------+
+-------------+--------------+
| SHUTNOK CMD | FAILURE CODE |
+-------------+--------------+
- SHUTDOWN CMD, SHUTOK CMD, SHUTNOK CMD 均为 1 字节长。
- RANDOM + SECRET KEY 的 MD5 均为 32 字符长。
- FAILURE CODE 为 32 位长。
扩展环境变量功能
注:在 JK 中处理 AJP13 时,我确实发现了“JkEnvVar”。以下“扩展环境变量功能”的描述可能不会在扩展 AJP13 中实现,因为它已在原始实现中可用。描述:许多用户会希望将其一些 Web 服务器环境变量传递给 Servlet 引擎。为了减少网络流量,Web-servlet 将发送一个表格以更短的方式描述外部变量。我们将在那里使用 AJP13 中已有的功能,即属性列表:在 AJP13 中,我们有
AJP13_FORWARD_REQUEST :=
prefix_code 2
method (byte)
protocol (string)
req_uri (string)
remote_addr (string)
remote_host (string)
server_name (string)
server_port (integer)
is_ssl (boolean)
num_headers (integer)
request_headers *(req_header_name req_header_value)
?context (byte string)
?servlet_path (byte string)
?remote_user (byte string)
?auth_type (byte string)
?query_string (byte string)
?route (byte string)
?ssl_cert (byte string)
?ssl_cipher (byte string)
?ssl_session (byte string)
?attributes *(attribute_name attribute_value)
request_terminator (byte)
+-------------------+---------------------------+-------------------------------+----+
| EXTENDED VARS CMD | WEB SERVER ATTRIBUTE NAME | SERVLET ENGINE ATTRIBUTE NAME | ES |
+-------------------+---------------------------+-------------------------------+----+
JkExtVars S1 SSL_CLIENT_V_START javax.servlet.request.ssl_start_cert_date
JkExtVars S2 SSL_CLIENT_V_END javax.servlet.request.ssl_end_cert_date
JkExtVars S3 SSL_SESSION_ID javax.servlet.request.ssl_session_id
+-------------------+----+-------------------------------------------+
| EXTENDED VARS CMD | S1 | javax.servlet.request.ssl_start_cert_date |
+-------------------+----+-------------------------------------------+
+----+-----------------------------------------+
| S2 | javax.servlet.request.ssl_end_cert_date |
+----+-----------------------------------------+
+----+-----------------------------------------+
| S3 | javax.servlet.request.ssl_end_cert_date |
+----+-----------------------------------------+
- EXTENDED VARS CMD 为 1 字节长。
- WEB SERVER ATTRIBUTE NAME, SERVLET ENGINE ATTRIBUTE NAME 均为 CString。
- ES 是一个空 CString。
上下文信息从 Servlet 引擎转发到 Web 服务器
在登录阶段之后,Web 服务器将请求 Servlet 引擎处理的上下文列表和 URL/URI。这将简化在许多站点的安装,减少关于 tomcat-user 邮件列表上配置的问题,并为 Servlet API 2.3 做好准备。此模式将通过一个新的指令 JkAutoMount 激活,例如:JkAutoMount examples myworker1 /examples/ 如果我们想获取 Servlet 引擎处理的所有上下文,可以使用通配符,例如:JkAutoMount * myworker1 * 一个 Servlet 引擎可以有许多上下文,例如 /examples、/admin、/test。我们可能只想为给定的 Worker 使用某些上下文。以前,例如在 Apache HTTP 服务器中,这是通过在 Apache 的每个 [virtual] 区域中手动设置相应的 JkMount 来完成的。如果您的 Web 服务器支持虚拟主机,我们也会将该信息转发给 Servlet 引擎,Servlet 引擎将只返回该虚拟主机的上下文。在这种情况下,Servlet 引擎将只返回与这些特定虚拟服务器(在 server.xml 中定义)匹配的 URL/URI。此功能将有助于 ISP 和大型站点在负载均衡配置中共同利用大型 Tomcat 集群。
+-----------------+-------------------+----------+----------+----+
| CONTEXT QRY CMD | VIRTUAL HOST NAME | CONTEXTA | CONTEXTB | ES |
+-----------------+-------------------+----------+----------+----+
+------------------+-------------------+----------+-------------------+----------+---------------+----+
| CONTEXT INFO CMD | VIRTUAL HOST NAME | CONTEXTA | URL1 URL2 URL3 ES | CONTEXTB | URL1 URL2 ... | ES |
+------------------+-------------------+----------+-------------------+----------+---------------+----+
- CONTEXT QRY CMD 和 CONTEXT INFO CMD 均为 1 字节长。
- VIRTUAL HOST NAME 是一个 CString,即一个以空字节 (/0) 终止的字符数组。
- 空字符串仅为一个空字节 (/0)。
- ES 是一个空 CString。表示 URI/URL 的结束或 CONTEXT 的结束。
当不使用虚拟模式时,VIRTUAL HOST NAME 为 '*'。在这种情况下,Servlet 引擎将发送所有已处理的上下文。
上下文信息从 Servlet 引擎更新到 Web 服务器
上下文更新是每次上下文被停用/重新激活时从 Servlet 引擎发出的消息。当使用 JkUpdateMount 指令时,更新将生效。此指令将设置 AJP13_CONTEXT_UPDATE_NEG 标志。例如:JkUpdateMount myworker1
+--------------------+-------------------+----------+--------+----------+--------+----+
| CONTEXT UPDATE CMD | VIRTUAL HOST NAME | CONTEXTA | STATUS | CONTEXTB | STATUS | ES |
+--------------------+-------------------+----------+--------+----------+--------+----+
- CONTEXT UPDATE CMD, STATUS 均为 1 字节长。
- VIRTUAL HOST NAME, CONTEXTS 均为 CString。
- ES 是一个空 CString。表示 CONTEXT 的结束。
当不使用虚拟模式时,VIRTUAL HOST NAME 为 '*'。STATUS 是一个字节,指示上下文是 UP/DOWN/INVALID
向 Servlet 引擎查询上下文状态
此查询将由 Web 服务器使用,以确定给定的上下文是 UP、DOWN 还是 INVALID(并应被移除)。
+-------------------+--------------------+----------+----------+----+
| CONTEXT STATE CMD | VIRTUAL HOST NAME | CONTEXTA | CONTEXTB | ES |
+-------------------+--------------------+----------+----------+----+
+-------------------------+-------------------+----------+--------+----------+--------+----+
| CONTEXT STATE REPLY CMD | VIRTUAL HOST NAME | CONTEXTA | STATUS | CONTEXTB | STATUS | ES |
+-------------------------+-------------------+----------+-------------------+--------+----+
- CONTEXT STATE CMD, CONTEXT STATE REPLY CMD, STATUS 均为 1 字节长。
- VIRTUAL HOST NAME, CONTEXTS 均为 CString
- ES 是一个空 CString
当不使用虚拟模式时,VIRTUAL HOST NAME 是一个空字符串。
未知数据包的处理
有时,即使是经过良好协商的协议,我们仍可能遇到一方(Web 服务器或 Servlet 引擎)接收到无法理解的消息的情况。在这种情况下,接收方将发送一个 'UNKNOW PACKET CMD' 并附带未处理的消息。
+--------------------+------------------------+-------------------+
| UNKNOWN PACKET CMD | UNHANDLED MESSAGE SIZE | UNHANDLED MESSAGE |
+--------------------+------------------------+-------------------+
- UNKNOWN PACKET CMD 为 1 字节长。
- UNHANDLED MESSAGE SIZE 为 16 位长。
- UNHANDLED MESSAGE 是一个字节数组(长度包含在 UNHANDLED MESSAGE SIZE 中)
已添加 UNHANDLED MESSAGE SIZE(开发中)
发送请求前验证连接
注:此功能可能永远不会被使用,因为它可能减慢正常进程,因为它需要在 Web 服务器端在转发请求之前进行额外的 IO(读取)操作..... Socket API 的一个优点是,您可以对半关闭的 Socket 进行写入操作。当 Servlet 引擎关闭 Socket 时,Web 服务器只有在下次对 Socket 进行 read() 操作时才会发现。基本上,在 AJP13 协议中,Web 服务器将 HTTP HEADER 和 HTTP BODY(以 8K 分块发送的 POST)发送到 Servlet 引擎,然后尝试接收回复。如果连接断开,Web 服务器只会在接收时发现。我们可以使用缓冲方案,但是当您使用 Servlet 引擎进行超过 8KB 数据的上传操作时会发生什么?AJP13 协议中的技巧是在服务结束后添加一些要读取的字节。
EXAMPLE OF DISCUSSION BETWEEN WEB SERVER AND SERVLET ENGINE
AJP HTTP-HEADER (+ HTTP-POST) (WEB->SERVLET)
AJP HTTP-REPLY (SERVLET->WEB)
AJP END OF DISCUSSION (SERVLET->WEB)
---> AJP STATUS (SERVLET->WEB AJP13)
+------------+-------------+
| STATUS CMD | STATUS DATA |
+------------+-------------+
- STATUS CMD 和 STATUS DATA 均为 1 字节长。