<-
Apache > HTTP Server > 文件 > 版本 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 Protocol 版本 1.3(以下簡稱 AJP13)的支援。

因此,若要取得處理 AJP13 協定的能力,伺服器中必須同時存在 mod_proxymod_proxy_ajp

警告

請在 確保伺服器安全 之前,不要啟用代理。開放式代理伺服器對您的網路和 Internet 整體而言都是危險的。

Support Apache!

主題

指令

此模組沒有提供任何指令。

除錯檢查清單

請參閱

top

使用情況

此模組用於使用 AJP13 協定,反向代理到後端應用程式伺服器(例如 Apache Tomcat)。用法類似於 HTTP 反向代理,但使用 ajp:// 前置詞

簡單反向代理

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

自 Tomcat 8.5.51 和 9.0.31 起,Tomcat 的 secret 等選項(預設為必要)可以新增為 ProxyPassBalancerMember 末端的獨立參數。此參數適用於 Apache HTTP Server 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(而不是後端 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 協定是封包導向。二進位格式可想而知是基於效能考量,而選擇比可讀性較高的純文字來使用。網路伺服器透過 TCP 連線與 servlet 容器進行通訊。為了減少建置通訊埠的昂貴程序,網路伺服器會嘗試維持到 servlet 容器的持久 TCP 連線,並重複使用連線以供多個請求/回應循環使用。

連線指派給某項特定請求後,在請求處理循環終止之前,不會用於任何其他請求。換句話說,請求並未透過連線多工化。雖然這樣會導致需要開啟更多連線才會使用,不過這會讓連線兩端的程式碼變得更簡單。

網路伺服器開啟到 servlet 容器的連線後,連線會處於下列狀態之一

連線指派給處理特定請求後,會在請求封包結構中,以極度濃縮的形式透過連線傳送基本請求資訊(例如 HTTP 標頭等)。如果請求中有一個本文 (content-length > 0),會在緊接著的個別封包中傳送。

在這個時候,presumably servlet 容器準備好開始處理請求了。在處理過程中,servlet 容器可以將下列訊息傳回網路伺服器

每一則訊息都附帶格式不同的資料封包。詳情請參閱下列回應封包結構。

top

基本封包結構

這個通訊協定有點承襲 XDR,但在許多方面有所不同(例如沒有 4 位元組對齊)。

AJP13 使用網路位元組順序處理所有資料類型。

協定中包含四種資料類型:位元組、布林、整數和字串。

位元組
單一位元組。
布林
單一位元組,1 = true0 = false。在有些地方使用其他非零值作為 true(例如 C 風格)可能是可行的,但在其他地方卻不行。
整數
範圍介於 0 到 2^16 (32768) 的數字。以兩個位元組儲存,高位元組在先。
字串
長度可變的字串(長度受 2^16 限制)。編碼方式將長度先打包成兩位元組,接著放上字串(包含結尾字元 '\0')。請注意編碼長度不包含結尾 '\0',和 strlen 類似。這會讓 Java 端有點混淆,因為到處充斥著奇特的自動遞增敘述,用來略過這些結尾字元。我相信這麼做的原因是讓 C 程式碼在讀取 servlet 容器傳回的字串時更有效率,這樣一來,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,後接封包長度),但之後沒有任何前置碼。

網頁伺服器可以傳送下列訊息給 Servlet 容器

代碼 封包類型 意義
2 轉送請求 使用下列資料開始請求處理循環
7 關閉 網頁伺服器要求容器關閉自身。
8 PING 網頁伺服器要求容器取得控制權(安全登入階段)。
10 CPING 網頁伺服器要求容器使用 CPONG 快速回應。
資料 大小(2 位元組)和對應的主體資料。

為了確保一些基本安全性,容器只有在請求來自它所寄存的機器時才會實際執行關閉

第一個資料封包會在網頁伺服器傳送轉送請求後立即傳送。

Servlet 容器可以傳送下列類型訊息給網頁伺服器

代碼 封包類型 意義
3 傳送主體區塊 從 Servlet 容器傳送主體區塊給網頁伺服器(並假設也會傳送到瀏覽器)。
4 傳送標頭 從 Servlet 容器傳送回應標頭給網頁伺服器(並假設也會傳送到瀏覽器)。
5 結束回應 標記回應結束(因而結束請求處理循環)。
6 取得主體區塊 如果仍未傳輸所有資料,則從請求取得更多資料。
9 CPONG 回覆 CPING 請求的回應

上述每則訊息皆有不同的內部結構,詳細說明如下。

top

請求封包結構

針對從伺服器傳送給 *轉送請求* 類型的容器之訊息

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 將傳輸其他方法,即使它們不在此清單中。

協定、req_uri、遠端地址、遠端主機、伺服器名稱、伺服器埠口、是否為 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
connection0xA006SC_REQ_CONNECTION
content-type0xA007SC_REQ_CONTENT_TYPE
content-length0xA008SC_REQ_CONTENT_LENGTH
cookie0xA009SC_REQ_COOKIE
cookie20xA00ASC_REQ_COOKIE2
host0xA00BSC_REQ_HOST
pragma0xA00CSC_REQ_PRAGMA
referer0xA00DSC_REQ_REFERER
user-agent0xA00ESC_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-request_terminator

目前的 C 程式碼並未設定 `context` 和 `servlet_path`,而大部分的 Java 程式碼則完全忽略那些欄位傳送過來的資料(其中有些如果在這些程式碼之後傳送字串,程式碼還會中斷)。我不知道這是不是臭蟲,還是未實作的功能,或者是僅留著的程式碼,但兩端的連線都不存在這個程式碼。

`remote_user` 和 `auth_type` 應該是參考 HTTP 等級的驗證,並傳達遠端使用者的使用者名稱和用於確認其身分的驗證類型(例如,Basic、Digest)。

`query_string`、`ssl_cert`、`ssl_cipher`、`ssl_session` 和 `ssl_key_size` 參考 HTTP 和 HTTPS 的對應部分。

`jvm_route` 用於支援固定會話(在存在多個負載平衡伺服器的情況下,將使用者的會話與特定 Tomcat 執行個體相連結)。

當 `secret=secret_keyword` 參數用於 `ProxyPass` 或 `BalancerMember` 指令時,會傳送 `secret`。後端需要支援 `secret`,而且值必須相符。`request.secret` 或 `requiredSecret` 記錄在 Apache Tomcat 的 AJP 設定檔中。

除了這些基本屬性清單之外,還能透過 `req_attribute` 程式碼 `0x0A` 傳送任何數量的其他屬性。一組用於表示屬性名稱和值的字串會在該程式碼的每個執行個體之後立即傳送。環境值是透過這個方法傳遞的。

最後,在傳送完所有屬性之後會傳送屬性終止程式碼 `0xFF`。這表示屬性清單的結尾,也是 Request Packet 的結尾。

top

Response Packet 結構

供容器傳送回伺服器使用的訊息。

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`)。回應標頭名稱的編碼方式與請求標頭名稱相同。相關如何區分程式碼和字串的詳細資訊,請參閱上方的標頭編碼。
常見標頭的程式碼如下

名稱代碼值
Content-Type0xA001
Content-Language0xA002
Content-Length0xA003
Date0xA004
Last-Modified0xA005
Location0xA006
Set-Cookie0xA007
Set-Cookie20xA008
Servlet-Engine0xA009
狀態0xA00A
WWW-Authenticate0xA00B

在程式碼或字串標頭名稱之後,會立即對標頭值進行編碼。

結束回應

指出此要求處理週期的結束。如果 reuse 旗標為 true(實際 C 程式碼中為 0 以外的數字),此 TCP 連線現在可接受新的要求。如果 reuse 為 false(==0),則應該關閉連線。

取得主體區塊

容器要求更多來自請求的資料(如果主體過大而無法放入第一個已傳送的封包,或在請求分塊時)。伺服器會根據 request_length、可傳送最大主體大小(8186(8 KB - 6))和請求主體實際上留下多少位元組要傳送中的最小值,傳送一個有資料量的回應主體封包。
如果主體中沒有更多資料(也就是說,servlet 容器試圖在主體結束之後進行讀取),伺服器會傳送一個空的封包,這是一個具有長度為 0 的有效負載的主體封包。(0x12,0x34,0x00,0x00)

可用語言:  en  |  fr  |  ja 

top

意見

注意事項
這裡並非問答區。在此發表的意見應針對改善文件或伺服器提出建議,且我們的管理員可能會刪除已實施或被視為無效或離題的意見。有關如何管理 Apache HTTP 伺服器應向我們的 IRC 頻道 #httpd(在 Libera.chat)尋求協助,或寄送至我們的郵件列表