mod_proxy_ajp
描述: | mod_proxy的 AJP 支持模块 |
状态: | 延期 |
模块标识符: | proxy_ajp_module |
源文件: | mod_proxy_ajp.c |
兼容性: | 可在 version 2.1 及更高版本中使用 |
摘要
该模块需要mod_proxy的服务。它为Apache JServ Protocol version 1.3
(以下简称 AJP13)提供支持。
因此,为了获得处理AJP13
协议的能力,必须在服务器中存在mod_proxy和mod_proxy_ajp。
警告
在有保护你的服务器之前不要启用代理。开放代理服务器对您的网络和整个 Internet 都是危险的。
用法
此模块用于使用 AJP13 协议将代理反向代理到后端 application 服务器(e.g. 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 请求包括给代理的原始 host 头,并且 application 服务器可以生成相对于此 host 的 self-referential headers,因此不需要重写。
主要的 exception 是代理上的 URL 路径与后端上的 URL 路径不同。在这种情况下,对于 example,可以相对于原始 host URL(而不是后端ajp://
URL)重写重定向标头:
重写代理路径
ProxyPass "/apps/foo" "ajp://backend.example.com:8009/foo" ProxyPassReverse "/apps/foo" "http://www.example.com/foo"
但是,通常最好将后端服务器上的 application 部署在与代理相同的路径上,而不是采用这种方法。
环境变量
名称前缀为AJP_
的环境变量将作为 AJP 请求属性(从 key 的 name 中删除 AJP前缀)转发到源服务器。
协议概述
AJP13
协议是 packet-oriented。由于 performance 的原因,可能会选择二进制格式而不是更易读的纯文本。 web 服务器通过 TCP 连接与 servlet 容器通信。为了减少 socket 创建的昂贵的 process,web 服务器将尝试维护到 servlet 容器的持久 TCP 连接,并重用多个 request/response 循环的连接。
将连接分配给特定请求后,在 request-handling 周期终止之前,不会将其用于任何其他请求。换句话说,请求不是通过连接多路复用的。这使得连接的任何一端都有更简单的 code,尽管它确实会导致更多的连接一次打开。
一旦 web 服务器打开了与 servlet 容器的连接,该连接就可以处于以下状态之一:
- 闲
没有通过此连接处理请求。 - 分配
连接正在处理特定请求。
一旦分配了连接来处理特定请求,基本请求信息(e.g. HTTP headers 等)就会以高度压缩的形式通过连接发送(e.g. common strings 被编码为整数)。请求数据包结构中包含该格式的详细信息。如果请求(content-length > 0)
有一个正文,那么紧接着就会在一个单独的数据包中发送。
此时,servlet 容器可能已准备好开始处理请求。在这样做时,它可以将以下消息发送回 web 服务器:
- SENDHEADERS
将一组 headers 发送回浏览器。 - SEND_BODY_CHUNK
将一大块正文数据发送回浏览器。 - GET_BODY_CHUNK
如果尚未全部转移请求,请从请求中获取更多数据。这是必要的,因为数据包具有固定的最大大小,并且任意数量的数据可以包含在请求的主体中(对于上传的 files,对于 example)。(注意:这与 HTTP 分块传输无关)。 - END_RESPONSE
完成 request-handling 循环。
每条消息都附有不同格式的数据包。有关详细信息,请参阅下面的响应包结构
基本数据包结构
这个协议有一些 XDR 遗产,但它有很多种方式(对于 example 没有 4 字节对齐)。
AJP13 对所有数据类型使用网络字节 order。
协议中有四种数据类型:字节,布尔值,整数和 strings。
- 字节
一个字节。 - **_**布尔
单个字节,1 = true,0 = false。使用其他 non-zero 值作为 true(i.e .C-style)可能在某些地方有效,但在其他地方则不然。 - 整数
数字范围为 0 到 2 ^ 16(32768)。首先以 high-order 字节存储在 2 个字节中。 - 串一个 variable-sized string(长度由 2 ^ 16 限制)。首先将长度编码为两个字节,然后是 string(包括终止' 0')。请注意,编码长度不包括尾部' 0'-它就像 strlen。这在 Java 方面令人困惑,其中充斥着奇怪的自动增量 statements 以跳过这些终结符。我相信这样做的原因是为了在读取 servlet 容器发回的 strings 时允许 C code 非常有效-使用终止的\ 0 字符,C code 可以将 references 传递到单个缓冲区,而无需复制。如果缺少\ 0,C code 将不得不在 order 中复制出来以获得 string 的概念。
包大小
根据 code 的大部分内容,最大数据包大小为8 * 1024 bytes(8K)
。数据包的实际长度在标头中编码。
包 Headers
从服务器发送到容器的数据包以0x1234
开头。从容器发送到服务器的数据包以AB
开头(这是 A 的 ASCII code,后跟 B 的 ASCII code)。在前两个字节之后,有一个 integer(如上编码)和有效载荷的长度。虽然这可能表明最大有效载荷可能大到 2 ^ 16,但事实上,code _set 最大值为 8K。
包格式(服务器->容器) | |||||
字节 | 0 | 1 | 2 | 3 | 4...(n 3) |
内容 | 0 x12 | 0 x34 | 数据长度(n) | 数据 |
包格式(容器->服务器) | |||||
字节 | 0 | 1 | 2 | 3 | 4...(n 3) |
内容 | 一个 | 乙 | 数据长度(n) | 数据 |
对于大多数数据包,有效负载的第一个字节编码消息类型。 exception 用于从服务器发送到容器的请求主体数据包-它们与标准数据包头(0x1234
然后是数据包的长度)一起发送,但之后没有任何前缀 code。
web 服务器可以将以下消息发送到 servlet 容器:
码 | 包的类型 | 含义 |
2 | 转发请求 | 使用以下数据开始 request-processing 循环 |
7 | 关掉 | web 服务器要求容器自行关闭。 |
8 | 平 | web 服务器要求容器进行控制(安全登录阶段)。 |
10 | CPing | web 服务器要求容器使用 CPong 快速响应。 |
没有 | 数据 | 大小(2 个字节)和相应的正文数据。 |
为了确保一些基本的安全性,如果请求来自托管它的同一台机器,容器将只实际执行Shutdown
。
web 服务器在Forward Request
之后立即发送第一个Data
数据包。
servlet 容器可以将以下类型的消息发送到 Web 服务器:
码 | 包的类型 | 含义 |
3 | 发送身体块 | 将身体的一部分从 servlet 容器发送到 web 服务器(可能是在浏览器上)。 |
4 | 发送 Headers | 将响应 headers 从 servlet 容器发送到 web 服务器(可能是在浏览器上)。 |
5 | 结束回应 | 标记响应的结束(因此 request-handling 循环)。 |
6 | 获取身体块 | 如果尚未全部转移请求,请从请求中获取更多数据。 |
9 | CPong 回复 | 对 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)
并非 all-important 标头是content-length
,因为它确定容器是否立即查找另一个数据包。
前向请求元素的详细描述
请求前缀
对于所有请求,这将是 2.有关其他前缀代码的详细信息,请参阅上文。
方法
HTTP 方法,编码为单个字节:
命令 Name | 码 |
OPTIONS | 1 |
得到 | 2 |
头 | 3 |
POST | 4 |
放 | 5 |
删除 | 6 |
跟踪 | 7 |
PROPFIND | 8 |
PROPPATCH | 9 |
MKCOL | 10 |
复制 | 11 |
移动 | 12 |
锁 | 13 |
开锁 | 14 |
ACL | 15 |
报告 | 16 |
VERSION-CONTROL | 17 |
报到 | 18 |
查看 | 19 |
取消签 | 20 |
搜索 | 21 |
MKWORKSPACE | 22 |
UPDATE | 23 |
标签 | 24 |
合并 | 25 |
BASELINE_CONTROL | 26 |
MKACTIVITY | 27 |
稍后 version 的 ajp13,将传输其他方法,即使它们不在此列表中。
协议,req_uri,remote_addr,remotehost,servername,serverport,is_ssl
这些都是 self-explanatory。这些都是必需的,并将针对每个请求发送。
Headers
request_headers
的结构如下:首先,编码 headers num_headers
的数量。然后,接下来是一系列头 name req_header_name
/value req_header_value
对。 Common 标题名称被编码为整数,以节省空间。如果头 name 不在基本_header 列表中,则它被正常编码(作为 string,具有前缀长度)。 common headers sc_req_header_name
列表及其代码如下(均为 case-sensitive):
名称 | Code value | 代码名称 |
接受 | 0 xA001 | SC_REQ_ACCEPT |
accept-charset | 0 xA002 | SC_REQ_ACCEPTCHARSET |
accept-encoding | 0 xA003 | SC_REQ_ACCEPT_ENCODING |
accept-language | 0 xA004 | SC_REQ_ACCEPT_LANGUAGE |
授权 | 0 xA005 | SC_REQ_AUTHORIZATION |
连接 | 0 xA006 | SC_REQ_CONNECTION |
content-type | 0 xA007 | SC_REQCONTENTTYPE |
content-length | 0 xA008 | SC_REQ_CONTENT_LENGTH |
曲奇饼 | 0 xA009 | SC_REQCOOKIE |
COOKIE2 | 0 xA00A | SC_REQCOOKIE2 |
主办 | 0 xA00B | SC_REQHOST |
编译 | 0 xA00C | SC_REQ_PRAGMA |
引荐 | 0 xA00D | SC_REQ_REFERER |
user-agent | 0 xA00E | SC_REQ_USER_AGENT |
读取此_code 的 Java code 抓取第一个 two-byte integer,如果它在最高有效字节中看到'0xA0'
,则它使用第二个字节中的 integer 作为头名称 array 的索引。如果第一个字节不是0xA0
,则假定 two-byte integer 是 string 的长度,然后读入。
这是假设没有标题名称的长度大于0x9FFF(==0xA000 - 1)
,这是完全合理的,尽管有点武断。
注意:
content-length
标头非常重要。如果它存在且 non-zero,则容器假定请求具有正文(POST 请求,对于 example),并立即从输入流中读取单独的数据包以获取该正文。
属性
前缀为?
(e.g.?context
)的属性都是可选的。对于每个,都有一个字节 code 来表示属性的类型,然后是 value(string 或 integer)。它们可以在任何 order 中发送(尽管 C code 总是在下面列出的 order 中发送它们)。发送一个特殊的终止 code 以表示可选属性列表的结尾。字节代码列表是:
信息 | Code Value | 类型 Value | 注意 |
?context | 0 x01 | - | 目前尚未实施 |
? servlet_path | 0 x02 | - | 目前尚未实施 |
? remote_user | 0 x03 | string | |
? auth_type | 0 x04 | string | |
? querystring | 0 x05 | string | |
? jvmroute | 0 x06 | string | |
? ssl_cert | 0 x07 | string | |
? ssl_cipher | 0 x08 | string | |
? sslsession | 0 x09 | string | |
? req_attribute | 0 x0A | string | Name(属性的 name 如下) |
? sslkey_size | 0 x0B | 整数 | |
are_done | 0 xFF | - | request_terminator |
和servlet_path
当前不是由 C code 设置的,并且大多数 Java code 完全忽略了为这些字段发送的任何内容(如果在其中一个代码之后发送 string,则其中一些实际上会 break)。我不知道这是一个 bug 还是一个未实现的 feature 或者只是残留的 code,但它在连接的两端都没有。
remote_user
和auth_type
可能是指 HTTP-level 身份验证,并传达 remote 用户的用户名和用于建立其身份的身份验证类型(e.g. Basic,Digest)。
query_string
,ssl_cert
,ssl_cipher
和ssl_session
指的是 HTTP 和 HTTPS 的相应部分。
jvm_route
用于支持粘性会话-在存在多个 load-balancing 服务器的情况下将用户的 sesson 与特定的 Tomcat 实例相关联。
除了这个基本属性列表之外,还可以通过req_attribute
code 0x0A
发送任意数量的其他属性。在 code 的每个实例之后立即发送一对表示属性 name 和 value 的 strings。环境值通过此方法传递。
最后,在发送了所有属性之后,将发送属性终止符0xFF
。这既指示属性列表的末尾,也指示请求包的结束。
响应数据包结构
对于容器可以发送回服务器的消息。
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)
细节:
发送身体块
块基本上是二进制数据,并直接发送回浏览器。
发送 Headers
状态 code 和消息是常见的 HTTP 事物(e.g.200
和OK
)。响应头名称的编码方式与请求头名称相同。有关如何将代码与 strings 区分开的详细信息,请参阅上面的 header_encoding。
common headers 的代码是:
名称 | Code value |
Content-Type | 0 xA001 |
Content-Language | 0 xA002 |
Content-Length | 0 xA003 |
日期 | 0 xA004 |
Last-Modified | 0 xA005 |
地点 | 0 xA006 |
Set-Cookie | 0 xA007 |
Set-Cookie2 | 0 xA008 |
Servlet-Engine | 0 xA009 |
状态 | 0 xA00A |
WWW-Authenticate | 0 xA00B |
在 code 或 string 标头 name 之后,标头 value 立即被编码。
结束回应
表示此 request-handling 周期结束。如果reuse
flag 是 true (anything other than 0 in the actual C code)
,则此 TCP 连接现在可用于处理新的传入请求。如果reuse
是 false(==0),则应关闭连接。
获取身体块
容器要求来自请求的更多数据(如果主体太大而不适合发送的第一个数据包或请求被分块)。服务器将发回一个正文包,其中包含的数据量是request_length
的最小值,最大发送正文大小(8186(8 Kbytes - 6))
,以及实际从请求正文发送的字节数。
如果正文中没有更多数据(i.e.servlet 容器正在尝试读取正文末尾),服务器将发回一个空数据包,这是一个有效负载长度为 0 的主体数据包。