<-
Apache > HTTP 服务器 > 文档 > 版本 2.4 > 模块

Apache 模块 mod_proxy_ajp

可用语言:  en  |  fr  |  ja 

描述用于 mod_proxy 的 AJP 支持模块
状态扩展
模块标识符proxy_ajp_module
源文件mod_proxy_ajp.c
兼容性在版本 2.1 及更高版本中可用

摘要

此模块需要 mod_proxy 的服务。它提供对Apache JServ 协议版本 1.3(以下简称AJP13)的支持。

因此,为了获得处理AJP13协议的能力,mod_proxymod_proxy_ajp 必须存在于服务器中。

警告

在您保护服务器之前,请勿启用代理。开放代理服务器对您的网络和整个互联网都构成危险。

Support Apache!

主题

指令

此模块不提供任何指令。

错误修复清单

另请参阅

top

用法

此模块用于使用 AJP13 协议反向代理到后端应用程序服务器(例如 Apache Tomcat)。用法类似于 HTTP 反向代理,但使用ajp:// 前缀

简单反向代理

ProxyPass "/app" "ajp://backend.example.com:8009/app"

诸如 Tomcat 的secret 选项(从 Tomcat 8.5.51 和 9.0.31 开始默认需要)之类的选项可以作为单独的参数添加到 ProxyPassBalancerMember 的末尾。此参数在 Apache HTTP 服务器 2.4.42 及更高版本中可用

带有secret 选项的简单反向代理

ProxyPass "/app" "ajp://backend.example.com:8009/app" secret=YOUR_AJP_SECRET

也可以使用负载均衡器

负载均衡器反向代理

<Proxy "balancer://cluster">
    BalancerMember "ajp://app1.example.com:8009" loadfactor=1
    BalancerMember "ajp://app2.example.com:8009" loadfactor=2
    ProxySet lbmethod=bytraffic
</Proxy>
ProxyPass "/app" "balancer://cluster/app"

请注意,通常不需要 ProxyPassReverse 指令。AJP 请求包含提供给代理的原始主机标头,并且可以预期应用程序服务器生成相对于此主机的自引用标头,因此不需要重写。

主要例外情况是代理上的 URL 路径与后端上的 URL 路径不同。在这种情况下,可以相对于原始主机 URL(而不是后端ajp:// URL)重写重定向标头,例如

重写代理路径

ProxyPass "/apps/foo" "ajp://backend.example.com:8009/foo"
ProxyPassReverse "/apps/foo" "http://www.example.com/foo"

但是,通常最好将应用程序部署在后端服务器上与代理相同的路径上,而不是采用这种方法。

top

环境变量

名称带有前缀AJP_ 的环境变量将作为 AJP 请求属性转发到源服务器(从键的名称中删除AJP_ 前缀)。

top

协议概述

AJP13 协议是面向数据包的。出于性能原因,二进制格式可能比更易读的纯文本格式更受欢迎。Web 服务器通过 TCP 连接与 Servlet 容器通信。为了减少昂贵的套接字创建过程,Web 服务器将尝试维护与 Servlet 容器的持久 TCP 连接,并为多个请求/响应周期重用连接。

一旦将连接分配给特定请求,它将不会用于任何其他请求,直到请求处理周期终止。换句话说,请求不会在连接上进行多路复用。这使得连接两端的代码变得更加简单,尽管它会导致一次打开更多连接。

一旦 Web 服务器打开了与 Servlet 容器的连接,连接就可以处于以下状态之一

一旦将连接分配给处理特定请求,基本请求信息(例如 HTTP 标头等)将以高度压缩的形式通过连接发送(例如,公共字符串被编码为整数)。该格式的详细信息在下面的请求数据包结构中给出。如果请求有正文(content-length > 0),则该正文将在紧随其后的单独数据包中发送。

此时,Servlet 容器可能已准备好开始处理请求。在处理请求时,它可以将以下消息发送回 Web 服务器

每条消息都附带一个格式不同的数据包。有关详细信息,请参见下面的响应数据包结构。

top

基本数据包结构

此协议有一些 XDR 遗产,但它在很多方面有所不同(例如,没有 4 字节对齐)。

AJP13 对所有数据类型使用网络字节序。

协议中有四种数据类型:字节、布尔值、整数和字符串。

字节
单个字节。
布尔值
单个字节,1 = true0 = false。使用其他非零值作为 true(即 C 样式)可能在某些地方有效,但在其他地方无效。
整数
范围在0 到 2^16 (32768) 之间的数字。存储在 2 个字节中,高位字节在前。
字符串
可变大小的字符串(长度受 2^16 限制)。编码方式是先将长度打包到两个字节中,然后是字符串(包括终止符 '\0')。请注意,编码后的长度包括尾随的 '\0' - 它类似于strlen。这在 Java 方面有点令人困惑,Java 中充斥着奇怪的自动递增语句来跳过这些终止符。我相信这样做是为了让 C 代码在读取 Servlet 容器发送回来的字符串时更加高效 - 由于存在终止符 \0 字符,C 代码可以传递对单个缓冲区的引用,而无需复制。如果缺少 \0,C 代码将不得不复制内容才能获得其对字符串的理解。

数据包大小

根据大部分代码,最大数据包大小为 8 * 1024 字节 (8K)。数据包的实际长度在标头中编码。

数据包标头

从服务器发送到容器的数据包以0x1234 开头。从容器发送到服务器的数据包以AB 开头(即 A 的 ASCII 码后跟 B 的 ASCII 码)。在头两个字节之后,有一个整数(如上编码),表示有效负载的长度。虽然这可能表明最大有效负载可以达到 2^16,但实际上,代码将最大值设置为 8K。

数据包格式(服务器->容器)
字节 0 1 2 3 4...(n+3)
内容 0x12 0x34 数据长度 (n) 数据
数据包格式(容器->服务器)
字节 0 1 2 3 4...(n+3)
内容 A B 数据长度 (n) 数据

对于大多数数据包,有效负载的第一个字节编码消息类型。例外情况是从服务器发送到容器的请求正文数据包 - 它们使用标准数据包标头( 0x1234 然后是数据包的长度)发送,但之后没有任何前缀代码。

Web 服务器可以将以下消息发送到 Servlet 容器

代码 数据包类型 含义
2 转发请求 使用以下数据开始请求处理周期
7 关闭 Web 服务器要求容器关闭自身。
8 Ping Web 服务器要求容器接管(安全登录阶段)。
10 CPing Web 服务器要求容器快速响应 CPong。
数据 大小(2 个字节)和相应正文数据。

为了确保基本安全性,容器只会从其托管的同一台机器接收请求时才会实际执行Shutdown

第一个Data 数据包在 Web 服务器发送Forward Request 之后立即发送。

Servlet 容器可以将以下类型的消息发送到 Web 服务器

代码 数据包类型 含义
3 发送正文块 将 Servlet 容器到 Web 服务器(以及浏览器)的正文块发送。
4 发送标头 将 Servlet 容器到 Web 服务器(以及浏览器)的响应标头发送。
5 结束响应 标记响应(以及请求处理周期)的结束。
6 获取正文块 如果请求尚未全部传输,则从请求中获取更多数据。
9 CPong 响应 对 CPing 请求的响应

上述每条消息都有不同的内部结构,详见下文。

top

请求数据包结构

对于从服务器发送到容器的类型为Forward Request 的消息

AJP13_FORWARD_REQUEST :=
    prefix_code      (byte) 0x02 = JK_AJP13_FORWARD_REQUEST
    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)
    attributes      *(attribut_name attribute_value)
    request_terminator (byte) OxFF

request_headers 具有以下结构

req_header_name :=
    sc_req_header_name | (string)  [see below for how this is parsed]

sc_req_header_name := 0xA0xx (integer)

req_header_value := (string)

attributes 是可选的,具有以下结构

attribute_name := sc_a_name | (sc_a_req_attribute string)

attribute_value := (string)

请注意,最重要的标头是content-length,因为它决定了容器是否立即查找另一个数据包。

转发请求元素的详细描述

请求前缀

对于所有请求,这将是 2。有关其他前缀代码的详细信息,请参见上文。

方法

HTTP 方法,编码为单个字节

命令名称代码
OPTIONS1
GET2
HEAD3
POST4
PUT5
DELETE6
TRACE7
PROPFIND8
PROPPATCH9
MKCOL10
COPY11
MOVE12
LOCK13
UNLOCK14
ACL15
REPORT16
VERSION-CONTROL17
CHECKIN18
CHECKOUT19
UNCHECKOUT20
SEARCH21
MKWORKSPACE22
UPDATE23
LABEL24
MERGE25
BASELINE_CONTROL26
MKACTIVITY27

更高版本的 ajp13 将传输其他方法,即使它们不在此列表中。

protocol, req_uri, remote_addr, remote_host, server_name, server_port, is_ssl

这些都相当不言自明。每个请求都需要这些,并且将为每个请求发送。

标头

request_headers 的结构如下:首先,编码标头的数量num_headers。然后,是一系列标头名称req_header_name / 值req_header_value 对。公共标头名称被编码为整数,以节省空间。如果标头名称不在基本标头列表中,则会以正常方式对其进行编码(作为字符串,带有前缀长度)。公共标头列表sc_req_header_name 及其代码如下(所有代码区分大小写)

名称代码值代码名称
accept0xA001SC_REQ_ACCEPT
accept-charset0xA002SC_REQ_ACCEPT_CHARSET
accept-encoding0xA003SC_REQ_ACCEPT_ENCODING
accept-language0xA004SC_REQ_ACCEPT_LANGUAGE
authorization0xA005SC_REQ_AUTHORIZATION
连接0xA006SC_REQ_CONNECTION
内容类型0xA007SC_REQ_CONTENT_TYPE
内容长度0xA008SC_REQ_CONTENT_LENGTH
cookie0xA009SC_REQ_COOKIE
cookie20xA00ASC_REQ_COOKIE2
主机0xA00BSC_REQ_HOST
pragma0xA00CSC_REQ_PRAGMA
引用0xA00DSC_REQ_REFERER
用户代理0xA00ESC_REQ_USER_AGENT

读取此内容的 Java 代码会获取前两个字节的整数,如果它在最高有效字节中看到一个 '0xA0',它会使用第二个字节中的整数作为头名称数组的索引。如果第一个字节不是 0xA0,它会假设两个字节的整数是字符串的长度,然后读取该字符串。

这基于这样的假设,即没有头名称的长度会大于 0x9FFF (==0xA000 - 1),这完全合理,尽管有点任意。

注意

content-length 头非常重要。如果它存在且不为零,容器会假设请求有一个主体(例如 POST 请求),并立即从输入流中读取一个单独的数据包以获取该主体。

属性

? 为前缀的属性(例如 ?context)都是可选的。对于每个属性,都有一个字节代码来指示属性的类型,然后是它的值(字符串或整数)。它们可以以任何顺序发送(尽管 C 代码始终按下面列出的顺序发送它们)。发送一个特殊的终止代码来表示可选属性列表的结束。字节代码列表如下:

信息代码值值类型注意
?context0x01-当前未实现
?servlet_path0x02-当前未实现
?remote_user0x03字符串
?auth_type0x04字符串
?query_string0x05字符串
?jvm_route0x06字符串
?ssl_cert0x07字符串
?ssl_cipher0x08字符串
?ssl_session0x09字符串
?req_attribute0x0A字符串名称(属性名称紧随其后)
?ssl_key_size0x0B整数
?secret0x0C字符串自 2.4.42 版本起支持
are_done0xFF-请求终止符

C 代码目前没有设置 contextservlet_path,并且大多数 Java 代码完全忽略了为这些字段发送的内容(并且其中一些代码如果在这些代码之后发送字符串,实际上会崩溃)。我不知道这是个错误、未实现的功能还是仅仅是残留代码,但它在连接的两端都缺失。

remote_userauth_type 可能指的是 HTTP 级别的身份验证,并传达远程用户的用户名以及用于建立其身份的身份验证类型(例如 Basic、Digest)。

query_stringssl_certssl_cipherssl_sessionssl_key_size 指的是相应的 HTTP 和 HTTPS 部分。

jvm_route 用于支持粘性会话——在存在多个负载均衡服务器的情况下,将用户的会话与特定的 Tomcat 实例关联起来。

当在 ProxyPassBalancerMember 指令中使用 secret=secret_keyword 参数时,会发送 secret。后端需要支持 secret,并且值必须匹配。request.secretrequiredSecret 在 Apache Tomcat 的 AJP 配置中有所说明。

除了这些基本属性列表之外,还可以通过 req_attribute 代码 0x0A 发送任意数量的其他属性。在每个代码实例之后,会立即发送一对字符串来表示属性名称和值。环境值通过这种方式传递。

最后,在发送完所有属性之后,会发送属性终止符 0xFF。这表示属性列表的结束,也表示请求数据包的结束。

top

响应数据包结构

用于容器可以发送回服务器的消息。

AJP13_SEND_BODY_CHUNK :=
  prefix_code   3
  chunk_length  (integer)
  chunk        *(byte)
  chunk_terminator (byte) Ox00


AJP13_SEND_HEADERS :=
  prefix_code       4
  http_status_code  (integer)
  http_status_msg   (string)
  num_headers       (integer)
  response_headers *(res_header_name header_value)

res_header_name :=
    sc_res_header_name | (string)   [see below for how this is parsed]

sc_res_header_name := 0xA0 (byte)

header_value := (string)

AJP13_END_RESPONSE :=
  prefix_code       5
  reuse             (boolean)


AJP13_GET_BODY_CHUNK :=
  prefix_code       6
  requested_length  (integer)

细节

发送正文块

块基本上是二进制数据,直接发送回浏览器。

发送标头

状态代码和消息是通常的 HTTP 内容(例如 200OK)。响应头名称的编码方式与请求头名称相同。有关代码如何与字符串区分的详细信息,请参见上面的 header_encoding。
常见头的代码如下:

名称代码值
Content-Type0xA001
Content-Language0xA002
Content-Length0xA003
Date0xA004
Last-Modified0xA005
Location0xA006
Set-Cookie0xA007
Set-Cookie20xA008
Servlet-Engine0xA009
状态0xA00A
WWW-Authenticate0xA00B

在代码或字符串头名称之后,会立即对头值进行编码。

结束响应

表示此请求处理周期的结束。如果 reuse 标志为真 (在实际的 C 代码中,除了 0 之外的任何值),则此 TCP 连接现在可以用于处理新的传入请求。如果 reuse 为假 (==0),则应关闭连接。

获取正文块

容器请求更多请求数据(如果主体太大而无法放入发送的第一个数据包中,或者当请求被分块时)。服务器将发送一个主体数据包,其中包含的数据量是 request_length、最大发送主体大小 (8186 (8 Kbytes - 6)) 和请求主体中实际剩余的字节数的最小值。
如果主体中没有更多数据(即 servlet 容器试图读取主体末尾之后的数据),服务器将发送一个数据包,这是一个有效载荷长度为 0 的主体数据包。(0x12,0x34,0x00,0x00)

可用语言:  en  |  fr  |  ja 

top

评论

注意
这不是一个问答部分。此处放置的评论应指向有关改进文档或服务器的建议,如果它们已实施或被认为无效/与主题无关,可能会被我们的版主删除。有关如何管理 Apache HTTP Server 的问题,应发送到我们的 IRC 频道 #httpd(在 Libera.chat 上),或发送到我们的 邮件列表