On this page
Apache 模块 mod_proxy_ajp
Description: | mod_proxy的 AJP 支持模块 |
---|---|
Status: | Extension |
Module Identifier: | proxy_ajp_module |
Source File: | mod_proxy_ajp.c |
Compatibility: | 在 2.1 版和更高版本中可用 |
Summary
该模块需要 mod_proxy的服务。它提供对Apache JServ Protocol version 1.3
(以下称为* AJP13 *)的支持。
因此,为了获得处理AJP13
协议的能力,服务器中必须存在mod_proxy和mod_proxy_ajp。
Warning
在拥有保护您的服务器之前,不要启用代理。开放式代理服务器对您的网络和整个 Internet 都是危险的。
Usage
此模块用于使用 AJP13 协议将代理反向代理到后端应用程序服务器(例如 Apache Tomcat)。用法类似于 HTTP 反向代理,但使用ajp://
前缀:
简单反向代理
ProxyPass "/app" "ajp://backend.example.com:8009/app"
也可以使用平衡器:
平衡器反向代理
<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 请求包括提供给代理的原始主机 Headers,并且可以期望应用程序服务器生成相对于该主机的自引用 Headers,因此无需重写。
主要的 exception 是代理上的 URL 路径与后端上的 URL 路径不同。在这种情况下,可以相对于原始主机 URL(而不是后端ajp://
URL)重写重定向 Headers,例如:
重写代理路径
ProxyPass "/apps/foo" "ajp://backend.example.com:8009/foo"
ProxyPassReverse "/apps/foo" "http://www.example.com/foo"
但是,通常最好将应用程序部署在与代理相同的后端服务器上,而不是采用这种方法。
Environment Variables
名称带有前缀AJP_
的环境变量将作为 AJP 请求属性转发到原始服务器(从密钥名称中删除 AJP 前缀)。
协议概述
AJP13
协议是面向数据包的。出于性能考虑,大概选择了更具可读性的纯文本格式的二进制格式。 Web 服务器通过 TCP 连接与 Servlet 容器进行通信。为了减少昂贵的套接字创建过程,Web 服务器将尝试维护与 Servlet 容器的持久 TCP 连接,并在多个请求/响应周期中重用连接。
将连接分配给特定请求后,在请求处理周期终止之前,该连接将不再用于其他任何请求。换句话说,请求不在连接上多路复用。尽管这样做确实会导致同时打开更多连接,但是这会使连接两端的代码更加简单。
Web 服务器打开与 Servlet 容器的连接后,该连接可以处于以下状态之一:
Idle
没有通过该连接处理任何请求。Assigned
连接正在处理特定请求。
一旦分配了连接以处理特定请求,便会以高度压缩的形式(例如,通用字符串编码为整数)通过连接发送基本请求信息(例如 HTTP Headers 等)。该格式的详细信息在下面的“请求数据包结构”中。如果请求(content-length > 0)
包含主体,则该主体将在紧随其后的单独数据包中发送。
至此,servlet 容器可能已准备好开始处理请求。这样,它可以将以下消息发送回 Web 服务器:
SEND_HEADERS
将一组标题发送回浏览器。SEND_BODY_CHUNK
将大量的身体数据发送回浏览器。GET_BODY_CHUNK
如果请求尚未全部传输,请从请求中获取更多数据。这是必需的,因为数据包具有固定的最大大小,并且可以在请求的正文中包含任意数量的数据(例如,对于上载的文件)。 (注意:这与 HTTP 分块传输无关)。END_RESPONSE
完成请求处理周期。
每个消息都带有不同格式的数据包。有关详细信息,请参见下面的响应数据包结构。
基本封包结构
该协议有一些 XDR 传统,但是在很多方面都存在差异(例如,没有 4 字节对齐)。
AJP13 对所有数据类型使用网络字节 Sequences。
协议中有四种数据类型:字节,布尔值,整数和字符串。
Byte
- 一个字节。
Boolean
- 单个字节
1 = true
,0 = false
。在某些地方使用其他非零值作为 true(即 C 风格)可能会起作用,但在其他地方则不会。
- 单个字节
Integer
0 to 2^16 (32768)
范围内的数字。存储在 2 个字节中,高位在前。
String
- 可变大小的字符串(长度以 2 ^ 16 为界)。编码时先将其长度打包成两个字节,然后是字符串(包括终止符' 0')。请注意,编码后的长度 不 包括结尾的“\0”,就像
strlen
一样。在 Java 方面,这让人有些困惑,因为到处都是奇怪的 autoincrement 语句来跳过这些终止符。我相信这样做的原因是,允许 C 代码在读取 servlet 容器发送回的字符串时更加高效-带有终止符\ 0,C 代码可以将引用传递到单个缓冲区中,而无需复制。如果缺少\ 0,则 C 代码必须将内容复制出来才能获得其字符串概念。
- 可变大小的字符串(长度以 2 ^ 16 为界)。编码时先将其长度打包成两个字节,然后是字符串(包括终止符' 0')。请注意,编码后的长度 不 包括结尾的“\0”,就像
Packet Size
根据大部分代码,最大数据包大小为8 * 1024 bytes (8K)
。数据包的实际长度在 Headers 中编码。
Packet Headers
从服务器发送到容器的数据包以0x1234
开头。从容器发送到服务器的数据包以AB
开头(这是 A 的 ASCII 码,然后是 B 的 ASCII 码)。在前两个字节之后,有一个有效载荷长度的整数(按上面的编码)。尽管这可能表明最大有效载荷可能高达 2 ^ 16,但实际上,代码将最大有效载荷设置为 8K。
数据包格式(服务器->容器) | |||||
---|---|---|---|---|---|
Byte | 0 | 1 | 2 | 3 | 4...(n+3) |
Contents | 0x12 | 0x34 | 资料长度(n) | Data |
数据包格式(容器->服务器) | |||||
---|---|---|---|---|---|
Byte | 0 | 1 | 2 | 3 | 4...(n+3) |
Contents | A | B | 资料长度(n) | Data |
对于大多数数据包,有效载荷的第一个字节对消息的类型进行编码。从服务器发送到容器的请求主体数据包是一个 exception-它们以标准数据包 Headers(0x1234
,然后是数据包的长度)发送,但之后没有任何前缀代码。
Web 服务器可以将以下消息发送到 Servlet 容器:
Code | 包类型 | Meaning |
2 | Forward Request | 使用以下数据开始请求处理周期 |
7 | Shutdown | Web 服务器要求容器关闭自身。 |
8 | Ping | Web 服务器要求容器进行控制(安全登录阶段)。 |
10 | CPing | Web 服务器要求容器用 CPong 快速响应。 |
none | Data | 大小(2 个字节)和相应的正文数据。 |
为确保某些基本安全性,如果请求来自与其托管的同一台计算机,则该容器实际上只会执行Shutdown
。
Web 服务器在Forward Request
之后立即发送第一个Data
数据包。
Servlet 容器可以将以下类型的消息发送到 Web 服务器:
Code | 包类型 | Meaning |
3 | 发送身体块 | 将主体的一部分从 servlet 容器发送到 Web 服务器(大概发送到浏览器)。 |
4 | Send Headers | 将响应 Headers 从 servlet 容器发送到 Web 服务器(大概发送到浏览器)。 |
5 | End Response | 标记响应的结束(并因此标记请求处理周期)。 |
6 | 得到身体块 | 如果请求尚未全部传输,请从请求中获取更多数据。 |
9 | CPong Reply | 对 CPing 请求的回复 |
以上每个消息都有一个不同的内部结构,下面将详细介绍。
请求数据包结构
对于从服务器到* 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
,因为它确定容器是否立即查找另一个数据包。
转发请求要素的详细说明
Request prefix
对于所有请求,该值为 2.有关其他前缀代码的详细信息,请参见上文。
Method
HTTP 方法,编码为单个字节:
Command Name | Code |
OPTIONS | 1 |
GET | 2 |
HEAD | 3 |
POST | 4 |
PUT | 5 |
DELETE | 6 |
TRACE | 7 |
PROPFIND | 8 |
PROPPATCH | 9 |
MKCOL | 10 |
COPY | 11 |
MOVE | 12 |
LOCK | 13 |
UNLOCK | 14 |
ACL | 15 |
REPORT | 16 |
VERSION-CONTROL | 17 |
CHECKIN | 18 |
CHECKOUT | 19 |
UNCHECKOUT | 20 |
SEARCH | 21 |
MKWORKSPACE | 22 |
UPDATE | 23 |
LABEL | 24 |
MERGE | 25 |
BASELINE_CONTROL | 26 |
MKACTIVITY | 27 |
更高版本的 ajp13,将传输其他方法,即使它们不在此列表中。
协议,req_uri,remote_addr,remote_host,server_name,server_port,is_ssl
这些都是不言自明的。这些都是必需的,并将针对每个请求发送。
Headers
request_headers
的结构如下:首先,对 Headersnum_headers
的数量进行编码。然后,紧接着一系列标题名称req_header_name
/值req_header_value
对。通用 Headers 名称编码为整数,以节省空间。如果 Headers 名称不在基本 Headers 列表中,则它会正常编码(作为字符串,带有前缀长度)。通用 Headerssc_req_header_name
的列表及其代码如下(均区分大小写):
Name | Code value | Code name |
accept | 0xA001 | SC_REQ_ACCEPT |
accept-charset | 0xA002 | SC_REQ_ACCEPT_CHARSET |
accept-encoding | 0xA003 | SC_REQ_ACCEPT_ENCODING |
accept-language | 0xA004 | SC_REQ_ACCEPT_LANGUAGE |
authorization | 0xA005 | SC_REQ_AUTHORIZATION |
connection | 0xA006 | SC_REQ_CONNECTION |
content-type | 0xA007 | SC_REQ_CONTENT_TYPE |
content-length | 0xA008 | SC_REQ_CONTENT_LENGTH |
cookie | 0xA009 | SC_REQ_COOKIE |
cookie2 | 0xA00A | SC_REQ_COOKIE2 |
host | 0xA00B | SC_REQ_HOST |
pragma | 0xA00C | SC_REQ_PRAGMA |
referer | 0xA00D | SC_REQ_REFERER |
user-agent | 0xA00E | SC_REQ_USER_AGENT |
读取此内容的 Java 代码将抓取前两个字节的整数,并且如果在最高有效字节中看到'0xA0'
,它将使用第二个字节中的整数作为 Headers 名称数组的索引。如果第一个字节不是0xA0
,则假定两个字节的整数是字符串的长度,然后将其读入。
这是基于这样的假设,即任何标题名称的长度都不会大于0x9FFF (==0xA000 - 1)
,这是完全合理的,尽管有些武断。
Note:
content-length
Headers 非常重要。如果存在且不为零,则容器假定该请求具有主体(例如 POST 请求),并立即从 Importing 流中读取单独的数据包以获取该主体。
Attributes
带有?
(例如?context
)前缀的属性都是可选的。对于每个属性,只有一个字节码来指示属性的类型,然后是其值(字符串或整数)。它们可以按任何 Sequences 发送(尽管 C 代码始终按下面列出的 Sequences 发送它们)。发送一个特殊的终止代码来表示可选属性列表的结尾。字节码列表为:
Information | Code Value | 价值类型 | Note |
?context | 0x01 | - | 目前尚未实施 |
?servlet_path | 0x02 | - | 目前尚未实施 |
?remote_user | 0x03 | String | |
?auth_type | 0x04 | String | |
?query_string | 0x05 | String | |
?jvm_route | 0x06 | String | |
?ssl_cert | 0x07 | String | |
?ssl_cipher | 0x08 | String | |
?ssl_session | 0x09 | String | |
?req_attribute | 0x0A | String | 名称(属性名称如下) |
?ssl_key_size | 0x0B | Integer | |
?secret | 0x0C | String | 从 2.4.42 开始支持 |
are_done | 0xFF | - | request_terminator |
C 代码当前未设置context
和servlet_path
,大多数 Java 代码完全忽略了为这些字段发送的任何内容(如果在其中一个代码之后发送字符串,则其中的一些实际上会中断)。我不知道这是错误还是未实现的功能,或者仅仅是残留的代码,但是在连接的两边都没有。
remote_user
和auth_type
大概是指 HTTP 级别的身份验证,并传达远程用户的用户名和用于构建其身份的身份验证类型(例如,基本,摘要)。
query_string
,ssl_cert
,ssl_cipher
和ssl_session
指的是 HTTP 和 HTTPS 的相应部分。
jvm_route
用于支持粘性会话-在存在多个负载平衡服务器的情况下,将用户的 sesson 与特定的 Tomcat 实例相关联。
除了基本属性列表之外,还可以通过req_attribute
代码0x0A
发送任意数量的其他属性。在该代码的每个实例之后立即发送一对代表属性名称和值的字符串。环境值通过此方法传递。
最后,在发送完所有属性之后,发送属性终止符0xFF
。这不仅向属性列表的末尾发出 signal,而且还向请求数据包的结束发出 signal。
响应报文结构
容器可以发送回服务器的消息。
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)
Details:
发送身体块
块基本上是二进制数据,并直接发送回浏览器。
Send Headers
状态代码和消息是常见的 HTTP 内容(例如200
和OK
)。响应头名称的 encodings 与请求头名称的 encodings 相同。有关如何将代码与字符串区分开的详细信息,请参见上面的 header_encoding。
常见 Headers 的代码为:
Name | Code value |
Content-Type | 0xA001 |
Content-Language | 0xA002 |
Content-Length | 0xA003 |
Date | 0xA004 |
Last-Modified | 0xA005 |
Location | 0xA006 |
Set-Cookie | 0xA007 |
Set-Cookie2 | 0xA008 |
Servlet-Engine | 0xA009 |
Status | 0xA00A |
WWW-Authenticate | 0xA00B |
在代码或字符串标题名称之后,标题值立即被编码。
End Response
发出此请求处理周期结束的 signal。如果reuse
标志为(anything other than 0 in the actual C code)
,则此 TCP 连接现在可用于处理新的传入请求。如果reuse
为 false(== 0),则应关闭连接。
得到身体块
容器从请求中请求更多数据(如果主体太大而无法容纳发送过来的第一个数据包,或者请求被分块时)。服务器将发送回带有一个数据量的主体数据包,该数据量是request_length
的最小值,最大发送主体大小(8186 (8 Kbytes - 6))
以及从请求主体实际剩余要发送的字节数。
如果主体中没有更多数据(即 servlet 容器尝试读取主体的末尾),则服务器将发回* empty *数据包,该数据包是有效载荷长度为 0 的主体数据包。(0x12,0x34,0x00,0x00)