描述: | AJP支持模块
mod_proxy |
---|---|
状态: | 延期 |
模块标识符: | proxy_ajp_module |
源文件: | mod_proxy_ajp.c |
兼容性: | 在2.1版及更高版本中可用 |
此模块需要的服务mod_proxy
。它为Apache JServ Protocol version 1.3
(以下称为
AJP13)提供支持
。
因此,为了得到处理的能力AJP13
协议,mod_proxy
并
mod_proxy_ajp
具有存在于该服务器。
在确保服务器安全之前,请勿启用代理。开放式代理服务器对您的网络和整个Internet都是危险的。
该模块用于使用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请求包括提供给代理的原始主机标头,并且可以期望应用程序服务器生成相对于此主机的自引用标头,因此无需重写。
主要的例外是代理上的URL路径与后端上的URL路径不同。在这种情况下,可以相对于原始主机URL(而不是后端ajp://
URL)重写重定向头,例如:
ProxyPass "/apps/foo" "ajp://backend.example.com:8009/foo" ProxyPassReverse "/apps/foo" "http://www.example.com/foo"
但是,通常最好将应用程序以与代理相同的路径部署在后端服务器上,而不是采用这种方法。
名称带有前缀的环境变量AJP_
将作为AJP请求属性转发到原始服务器(从密钥名称中删除AJP_前缀)。
该AJP13
协议是面向分组的。出于性能考虑,大概选择了更具可读性的纯文本格式的二进制格式。Web服务器通过TCP连接与Servlet容器进行通信。为了减少昂贵的套接字创建过程,Web服务器将尝试维护与Servlet容器的持久TCP连接,并在多个请求/响应周期中重用连接。
将连接分配给特定请求后,在请求处理周期终止之前,该连接将不再用于其他任何请求。换句话说,请求不会通过连接进行多路复用。尽管这样做确实会导致同时打开更多连接,但是这会使连接两端的代码更加简单。
Web服务器打开与servlet容器的连接后,该连接可以处于以下状态之一:
一旦分配了连接以处理特定请求,就以高度压缩的形式(例如,将通用字符串编码为整数)在连接上发送基本请求信息(例如HTTP标头等)。该格式的详细信息在下面的“请求数据包结构”中。如果请求有正文(content-length > 0)
,则在随后的单独数据包中发送该正文。
此时,servlet容器可能已准备好开始处理请求。这样,它可以将以下消息发送回Web服务器:
每个消息都带有不同格式的数据包。有关详细信息,请参见下面的响应数据包结构。
该协议有一些XDR传统,但是在很多方面都存在差异(例如,没有4字节对齐)。
AJP13对所有数据类型使用网络字节顺序。
协议中有四种数据类型:字节,布尔值,整数和字符串。
1 = true
,0 = false
。在某些地方使用其他非零值作为true(即C样式)可能会起作用,但在其他地方则不会。0 to 2^16 (32768)
。存储在2个字节中,高位在前。strlen
。在Java方面,这让人有些困惑,因为到处都是奇怪的自动增量语句,以跳过这些终止符。我相信这样做是为了让C代码在读取servlet容器发送回的字符串时更加高效-使用终止符\ 0,C代码可以将引用传递到单个缓冲区中,而无需复制。如果缺少\ 0,则C代码必须将内容复制出来才能获得其字符串概念。根据大部分代码,最大数据包大小为
8 * 1024 bytes (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) |
内容 | 一个 | 乙 | 资料长度(n) | 数据 |
对于大多数数据包,有效载荷的第一个字节对消息的类型进行编码。从服务器发送到容器的请求主体数据包是一个例外-它们使用标准数据包头(
0x1234
然后是数据包的长度)发送,但之后没有任何前缀代码。
Web服务器可以将以下消息发送到Servlet容器:
码 | 包类型 | 含义 |
2 | 转发请求 | 使用以下数据开始请求处理周期 |
7 | 关掉 | Web服务器要求容器关闭自身。 |
8 | 平 | Web服务器要求容器进行控制(安全登录阶段)。 |
10 | ing | Web服务器要求容器用CPong快速响应。 |
没有 | 数据 | 大小(2个字节)和相应的正文数据。 |
为了确保一些基本的安全性,只有Shutdown
在请求来自与其托管的同一台计算机上时,容器才会实际执行操作
。
第一个Data
数据包Forward Request
在Web服务器之后立即发送
。
Servlet容器可以将以下类型的消息发送到Web服务器:
码 | 包类型 | 含义 |
3 | 发送身体块 | 将主体的一部分从Servlet容器发送到Web服务器(大概发送到浏览器)。 |
4 | 发送标题 | 将响应标头从servlet容器发送到Web服务器(大概发送到浏览器)。 |
5 | 结束回应 | 标记响应的结束(并因此标记请求处理周期)。 |
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)
并不是最重要的标头是content-length
,因为它确定容器是否立即查找另一个数据包。
对于所有请求,该值为2。有关其他前缀代码的详细信息,请参见上文。
HTTP方法,编码为一个字节:
指令名称 | 码 |
选项 | 1个 |
得到 | 2 |
头 | 3 |
开机自检 | 4 |
放 | 5 |
删除 | 6 |
跟踪 | 7 |
PROPFIND | 8 |
道具 | 9 |
MKCOL | 10 |
复制 | 11 |
移动 | 12 |
锁 | 13 |
开锁 | 14 |
访问控制列表 | 15 |
报告 | 16 |
版本控制 | 17 |
报到 | 18岁 |
查看 | 19 |
取消结帐 | 20 |
搜索 | 21 |
麦考克空间 | 22 |
更新 | 23 |
标签 | 24 |
合并 | 25 |
BASELINE_CONTROL | 26 |
活动能力 | 27 |
更高版本的ajp13,将传输其他方法,即使它们不在此列表中也是如此。
这些都是不言自明的。这些都是必需的,并将针对每个请求发送。
其结构request_headers
如下:首先,对报头数进行num_headers
编码。然后,出现一系列标题名称req_header_name
/值
req_header_value
对。通用标头名称编码为整数,以节省空间。如果标头名称不在基本标头列表中,则它会正常编码(作为字符串,带有前缀长度)。常见标头sc_req_header_name
及其代码的列表如下(均区分大小写):
名称 | 代码值 | 代码名称 |
接受 | 0xA001 | SC_REQ_ACCEPT |
接受字符集 | 0xA002 | SC_REQ_ACCEPT_CHARSET |
接受编码 | 0xA003 | SC_REQ_ACCEPT_ENCODING |
接受语言 | 0xA004 | SC_REQ_ACCEPT_LANGUAGE |
授权书 | 0xA005 | SC_REQ_AUTHORIZATION |
连接 | 0xA006 | SC_REQ_CONNECTION |
内容类型 | 0xA007 | SC_REQ_CONTENT_TYPE |
内容长度 | 0xA008 | SC_REQ_CONTENT_LENGTH |
曲奇饼 | 0xA009 | SC_REQ_COOKIE |
cookie2 | 0xA00A | SC_REQ_COOKIE2 |
主办 | 0xA00B | SC_REQ_HOST |
语用 | 0xA00C | SC_REQ_PRAGMA |
推荐人 | 0xA00D | SC_REQ_REFERER |
用户代理 | 0xA00E | SC_REQ_USER_AGENT |
读取此内容的Java代码将抓取前两个字节的整数,并且如果'0xA0'
在最高有效字节中看到,则会使用第二个字节中的整数作为标头名称数组的索引。如果第一个字节不是0xA0
,则假定两个字节的整数是字符串的长度,然后将其读入。
这是基于这样的假设,即没有头名称的长度应大于0x9FFF (==0xA000 - 1)
,这是完全合理的,尽管有些武断。
content-length
头部是非常重要的。如果存在且不为零,则容器假定该请求具有主体(例如POST请求),并立即从输入流中读取单独的数据包以获取该主体。
带有前缀?
(例如?context
)的属性都是可选的。对于每个属性,只有一个字节码来指示属性的类型,然后是其值(字符串或整数)。它们可以以任何顺序发送(尽管C代码始终按下面列出的顺序发送它们)。发送一个特殊的终止代码来表示可选属性列表的结尾。字节码列表为:
信息 | 代码值 | 价值类型 | 注意 |
上下文 | 0x01 | -- | 目前尚未实施 |
servlet_path | 0x02 | -- | 目前尚未实施 |
?remote_user | 0x03 | 串 | |
?auth_type | 0x04 | 串 | |
?请求参数 | 0x05 | 串 | |
?jvm_route | 0x06 | 串 | |
?ssl_cert | 0x07 | 串 | |
?ssl_cipher | 0x08 | 串 | |
?ssl_session | 0x09 | 串 | |
?req_attribute | 0x0A | 串 | 名称(属性名称如下) |
?ssl_key_size | 0x0B | 整数 | |
?秘密 | 0x0C | 串 | 从2.4.42开始支持 |
完成 | 0xFF | -- | request_terminator |
将context
和servlet_path
目前不被C代码设置,并且无论是在发送这些字段(如果一个字符串的代码中的一个后一起发送一些它实际上将打破)大部分的Java代码的完全忽略。我不知道这是错误还是未实现的功能,或者仅仅是残留代码,但连接的两侧都没有。
的remote_user
和auth_type
可能是指HTTP级认证,并进行通信远程用户的用户名和用于建立他们的身份(例如基本,摘要)身份验证的类型。
的query_string
,ssl_cert
,
ssl_cipher
,和ssl_session
指HTTP和HTTPS的对应件。
的jvm_route
,用于支持粘性会话-关联用户与多个,负载平衡服务器存在一个特定的Tomcat实例雪村。
除了基本属性列表之外,还可以通过req_attribute
code 发送任意数量的其他属性0x0A
。在该代码的每个实例之后立即发送一对代表属性名称和值的字符串。环境值通过此方法传递。
最后,在发送完所有属性后,将发送属性终止符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)
块基本上是二进制数据,并直接发送回浏览器。
状态代码和消息是常见的HTTP内容(例如200
和OK
)。响应头名称的编码方式与请求头名称的编码方式相同。有关如何将代码与字符串区分开的详细信息,请参见上面的header_encoding。
常见标头的代码为:
名称 | 代码值 |
内容类型 | 0xA001 |
内容语言 | 0xA002 |
内容长度 | 0xA003 |
日期 | 0xA004 |
最后修改 | 0xA005 |
位置 | 0xA006 |
Set-Cookie | 0xA007 |
Set-Cookie2 | 0xA008 |
Servlet引擎 | 0xA009 |
状态 | 0xA00A |
WWW认证 | 0xA00B |
在代码或字符串标题名称之后,标题值立即被编码。
发出此请求处理周期结束的信号。如果该
reuse
标志为true (anything other than 0 in the actual
C code)
,则此TCP连接现在可用于处理新的传入请求。如果reuse
为假(== 0),则应关闭连接。
容器从请求中请求更多数据(如果主体太大而无法容纳发送过来的第一个数据包,或者请求被分块时)。服务器将发送回带有一个数据量的主体数据包,该数据量是的最小值request_length
,最大发送主体大小(8186 (8 Kbytes - 6))
以及从请求主体实际剩余要发送的字节数。
如果主体中没有更多数据(即servlet容器正尝试读取主体的末尾),则服务器将发回一个
空数据包,该数据包是有效载荷长度为0的主体数据包。
(0x12,0x34,0x00,0x00)