AJPv13 扩展提案

简介

本文档是当前 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)

高级登录

  1. WEB-SERVER 发送 LOGIN INIT CMD + NEGOCIATION DATA + WEB SERVER INFO
  2. TOMCAT 回复 LOGIN SEED CMD + RANDOM DATA
  3. WEB-SERVER 计算 RANDOM DATA+SECRET DATA 的 MD5
  4. WEB-SERVER 发送 LOGIN COMP CMD + MD5 (SECRET DATA + RANDOM DATA)
  5. TOMCAT 回复 LOGIN STATUS CMD + NEGOCIED DATA + SERVLET ENGINE INFO
为防止 DOS 攻击,Servlet 引擎只会等待 LOGIN CMD 15/30 秒,并报告超时异常供管理员调查。登录命令将包含基本的协议协商信息,如压缩能力、加密、上下文信息(启动时)、运行时上下文更新(启动/停止)、SSL 环境变量级别、支持的 AJP 协议级别(level1/level2/level3...)。Web 服务器信息将包含 Web 服务器信息和连接器名称(例如 Apache 1.3.26 + mod_ssl 2.8.8 + mod_jk 1.2.41 + mod_perl 1.25)。Servlet 引擎将用其自身的掩码(表示其能力)来掩盖协商掩码,并在登录被接受时返回它。这将有助于在 Web 服务器端实现基本的 AJP13(level 1),并与 Servlet 引擎端更高级的协议处理程序协同工作,反之亦然。AJP13 设计得小巧且快速,因此 Web 服务器中存在的许多 SSL 信息不会转发到 Servlet 引擎。我们在此添加了四个协商标志,以提供更多关于客户端 SSL 数据(证书)、服务器 SSL 数据、所用加密和杂项数据(超时...)的信息。

消息流

+----------------+------------------+-----------------+
| 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。
密钥将通过 workers.properties 中的新属性 secretkey 设置。
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)
使用简短的“Web 服务器属性名称”将减少网络流量。
+-------------------+---------------------------+-------------------------------+----+
| 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 |
+----+-----------------------------------------+
在扩展 AJP13 的传输过程中,我们将看到属性名称包含 S1、S2、S3,属性值包含 2001/01/03、2002/01/03、0123AFE56。此示例展示了扩展 SSL 变量的使用,但任何“个人”的 Web 服务器变量,例如自定义身份验证变量,都可以在 Servlet 引擎中重复使用。开销仅是 AJP 流量中增加的几个字节。
  • 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 |
+------------------+-------------------+----------+-------------------+----------+---------------+----+
我们将通过上下文查询发现远程 Servlet 引擎处理的 URL/MIMES 列表,针对一系列上下文。在通配符模式下,CONTEXTA 将只包含 '*'。
  • 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)
AJP 状态不会在请求/响应 #N 结束时被 Servlet 引擎读取,而是在下一个会话开始时读取。此外,届时 Web 服务器还可以使用依赖于操作系统的功能(或更好的 APR 功能)来确定是否还有更多数据可读。这些数据可以是上下文更新。这将避免 Web 服务器向已停用的上下文发送请求。在这种情况下,如果使用负载均衡,它将寻找另一个 Servlet 引擎来处理请求。此功能将有助于 ISP 和拥有 Tomcat 集群的大型站点在不中断服务的情况下更新其 Servlet 引擎。
+------------+-------------+
| STATUS CMD | STATUS DATA |
+------------+-------------+
  • STATUS CMD 和 STATUS DATA 均为 1 字节长。

结论

扩展 AJP13 协议的目标是克服 AJP13 原始的一些限制。它提供了更简单的配置、对大型站点和 Tomcat 集群更好的支持、简单的认证系统以及协议更新的预留。在 JK(原生)和 Servlet 引擎(Java)中使用稳定的 ajp13 实现,这是对广为人知的 ajp13 的合理演进。

扩展 AJP13 中的命令和 ID 索引

AJP13 协议中要添加的命令和 ID 索引

命令 ID

命令名称命令编号
AJP13_LOGINIT_CMD0x10
AJP13_LOGSEED_CMD0x11
AJP13_LOGCOMP_CMD0x12
AJP13_LOGOK_CMD0x13
AJP13_LOGNOK_CMD0x14
AJP13_CONTEXT_QRY_CMD0x15
AJP13_CONTEXT_INFO_CMD0x16
AJP13_CONTEXT_UPDATE_CMD0x17
AJP13_STATUS_CMD0x18
AJP13_SHUTDOWN_CMD0x19
AJP13_SHUTOK_CMD0x1A
AJP13_SHUTNOK_CMD0x1B
AJP13_CONTEXT_STATE_CMD0x1C
AJP13_CONTEXT_STATE_REP_CMD0x1D
AJP13_UNKNOW_PACKET_CMD0x1E

协商标志

命令名称编号描述
AJP13_CONTEXT_INFO_NEG0x80000000Web 服务器在登录后需要上下文信息
AJP13_CONTEXT_UPDATE_NEG0x40000000Web 服务器需要上下文更新
AJP13_GZIP_STREAM_NEG0x20000000Web 服务器需要压缩流
AJP13_DES56_STREAM_NEG0x10000000Web 服务器需要使用密钥的 DES56 加密流
AJP13_SSL_VSERVER_NEG0x08000000关于服务器 SSL 变量的扩展信息
AJP13_SSL_VCLIENT_NEG0x04000000关于客户端 SSL 变量的扩展信息
AJP13_SSL_VCRYPTO_NEG0x02000000关于加密 SSL 变量的扩展信息
AJP13_SSL_VMISC_NEG0x01000000关于杂项 SSL 变量的扩展信息

协商 ID编号描述
AJP13_PROTO_SUPPORT_AJPXX_NEG0x00FF0000支持协议的掩码
AJP13_PROTO_SUPPORT_AJP13L1_NEG0x00010000通信可以使用 AJP13 级别 1
AJP13_PROTO_SUPPORT_AJP13L2_NEG0x00020000通信可以使用 AJP13 级别 2
AJP13_PROTO_SUPPORT_AJP13L3_NEG0x00040000通信可以使用 AJP13 级别 3

所有其他标志必须设置为 0,因为它们保留供将来使用。

失败 ID

失败 ID编号
AJP13_BAD_KEY_ERR0xFFFFFFFF
AJP13_ENGINE_DOWN_ERR0xFFFFFFFE
AJP13_RETRY_LATER_ERR0xFFFFFFFD
AJP13_SHUT_AUTHOR_FAILED_ERR0xFFFFFFFC

状态

失败 ID编号
AJP13_CONTEXT_DOWN0x01
AJP13_CONTEXT_UP0x02
AJP13_CONTEXT_OK0x03