mod_proxy_ajp
mod_proxy
で AJP をサポートするためのモジュール
Extension
mod_proxy_ajp.c
proxy_ajp_module

本モジュールには mod_proxy が必要です。 `Apache JServ Protocol version 1.3` (以降 AJP13) をサポートします。

`AJP13` プロトコルを扱えるようにするには mod_proxy と mod_proxy_ajp をサーバに組み込む必要があります。

# 警告

安全なサーバにするまでプロクシ機能は有効にしないでください。 オープンプロキシサーバはあなた自身のネットワークにとっても、 インターネット全体にとっても危険です。

mod_proxy

# プロトコルの概要

`AJP13` プロトコルはパケット指向です。 可読なプレーンテキスト形式ではなくバイナリ形式になったのは、 おそらくパフォーマンス上の理由によります。 ウェブサーバはサーブレットコンテナと TCP コネクションで通信します。 ソケット生成は重い処理なので、負荷を減らすために、サーブレットコンテナとの TCP 接続を維持し、複数のリクエスト・レスポンス処理サイクルに対して一つの コネクションを使いまわすようになっています。

あるリクエストにコネクションが割り当てられると、その処理サイクルが 完了するまで他のものに使われることはありません。 つまりコネクション上では、リクエストの同時処理は行われません。 このため、コネクション両端での実行するコードを簡潔にできる一方で、 同時に開くコネクションは多くなっています。

サーブレットコンテナへのコネクションを開いた後は、コネクションの状態は 次のどれかになります:

- Idle
  コネクション上で処理されているリクエストはありません。
- Assigned
  コネクションはリクエストを処理中です。

コネクションが特定のリクエストにアサインされると、基本的な情報 (例えば HTTP ヘッダ等) が圧縮された形 (例えば通常の文字列は整数にエンコードされます) で転送されます。詳細は下記の「リクエストパケットの構造」を参照してください。 リクエストにボディが存在 `(content-length > 0)` すれば、 基本的な情報の直後に別パケットで転送されます。

この時点でおそらく、サーブレットコンテナは処理を開始できるようになります。 ですので、次のメッセージをウェブサーバに戻して知らせられるようになります。

- SEND_HEADERS
  ブラウザにヘッダを送信します。
- SEND_BODY_CHUNK
  ブラウザにボディデータのチャンクを送ります。
- GET_BODY_CHUNK
  リクエストのデータを全て受け取り終わっていないときに、 残っているデータを受け取ります。パケットにある定まった最大長があり、任意の 大きさのデータがリクエストのボディとして含まれうる場合 (例えばファイルのアップロードの場合) に必要となります。 (注: HTTP のチャンク転送とは関連ありません。)
- END_RESPONSE
  リクエスト処理サイクルを終了します。

個々のメッセージはそれぞれ異なるデータパケット形式になっています。 後述の「レスポンスパケットの構造」を参照してください。

# 基本パケット構造

このプロトコルには XDR から受け継いだ部分が少しありますが、多くの点で 異なります (例えば 4 バイトアライメントでないことなど) 。

バイトオーダー: 個々のバイトのエンディアンがどうなっているかは、 私は詳しくないのですが、リトルエンディアンになっていると思います。 XDR 仕様でそうなっているのと、素晴らしいことに sys/socket ライブラリが (C で) そういう風にできているのでそうなのだと思いました。 ソケット呼び出しの内部についてより詳しい方がいらっしゃいましたら、 ご教授ください。

プロトコルには 4 つのデータタイプがあります: byte, boolean, integer, string です。

Byte
  バイト一つです。
Boolean
  バイト一つで、`1 = true`, `0 = false` です。 (C のように) 非零を真として扱ってしまうと、ある場合は動くかもしれませんし、 動かないかもしれません。
Integer
  `0 から 2^16 (32768)` の範囲の数字。高次の 2 バイトが 先に格納されます。
String
  可変長の文字列 (2^16 が長さの上限) 。長さ情報のパケット 2 バイトの後に 文字列 (終端文字 '\0' を含む) が続く形式でエンコードされます。 エンコードされている長さ情報は最後の '\0' をカウントしない ことに注意してください――これは `strlen` と同様です。 これらの終端文字をスキップするために、あまり意味の無いインクリメント文 をたくさん書かないといけないのは、 Java の側から見ると少し紛らわしく感じられるかもしれません。 こうなった理由はおそらく、Servlet コンテナから返される文字列を読み出す時に、 効率よく C のコードを書けるようにする――サーブレットから返される 文字列は \0 文字で終端されているので、C のコードではわざわざコピーをせずに、 一つのバッファへのリファレンスを取り回すように書くことができる―― ためだと思われます。 '\0' 文字がない場合は、C では文字列の規則に合うようにコピーしなければ いけなくなってしまいます。

## パケットサイズ

多くのコードでそうなっているのですが、パケットサイズの最大サイズは `8 * 1024 (8K)` です。パケットの実際の長さはヘッダに エンコードされて入っています。

## パケットヘッダ

サーバからコンテナに送出されるパケットは `0x1234` で始まります。 コンテナからサーバに送られるパケットは `AB` (ASCII コード A と ASCII コード B) で始まります。この二バイトの後に、ペイロード長が (上記の形式で) 続きます。このため、ペイロード長の最大値は 2^16 にできるように思えますが、 実際にはコードでは最大値は 8K に設定されています。

パケット形式 (Server->Container)
Byte | 0 | 1 | 2 | 3 | 4...(n+3)
Contents | 0x12 | 0x34 | データ長 (n) | Data

パケット形式 (Container->Server)
Byte | 0 | 1 | 2 | 3 | 4...(n+3)
Contents | A | B | データ長 (n) | Data

ほとんどのパケットで、ペイロードの最初のバイトがメッセージの型をエンコード しています。例外はサーバからコンテナに送られるリクエストボディパケットです ――これらは標準的なパケット形式 (`0x1234` とパケット長) ですが、その後に続くプレフィックスコードがありません。

ウェブサーバは次のメッセージをサーブレットコンテナに送出できます。

コード | パケットの型 | 意味
2 | Forward Request | リクエスト処理サイクルを後続のデータとともに開始する。
7 | Shutdown | ウェブサーバがコンテナに、コンテナを終了するように伝える。
8 | Ping | ウェブサーバがコンテナに制御を受け持つように伝える (セキュアログインフェーズ) 。
10 | CPing | ウェブサーバがコンテナに CPong で即座に応答するように伝える。
none | Data | サイズ (2 バイト) とそれに続くボディデータ。

基本的なセキュリティを確保するため、ホストされているマシンと同一の マシンからのリクエストに対してのみ、コンテナは実際に `Shutdown` を実行します。

最初の `Data` パケットは、`Forward Request` の直後にウェブサーバから送られます。

サーブレットコンテナはウェブサーバに、次のタイプのメッセージを送ることが できます :

コード | パケットの型 | 意味
3 | Send Body Chunk | サーブレットコンテナからウェブサーバに (そしておそらくそのままブラウザに)、ボディのチャンクを送る。
4 | Send Headers | サーブレットコンテナからウェブサーバに (そしておそらくそのままブラウザに) レスポンスヘッダを送る。
5 | End Response | レスポンス (つまりリクエスト処理サイクル) 終了の目印を送る。
6 | Get Body Chunk | まだ全て転送されていない場合、残っているリクエストのデータを受け取る。
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)

`属性` はオプションで、次のような構造をしています :

    attribute_name := sc_a_name | (sc_a_req_attribute string)

    attribute_value := (string)

もっとも重要なヘッダは `content-length` だということに 注意してください。コンテナは次のパケットを探すかどうかを、 それを見て決めるからです。

## Forward Request 要素の詳細な説明

## Request prefix

リクエストについては全て、この値は 2 になります。他の Prefix コードの詳細は 上記をご覧ください。

## Method

HTTP メソッドは 1 バイトにエンコードされます :

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 バージョンでは、この一覧にない、今後追加されるメソッドを 送るかもしれません。

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

これらはまさに文字通りのものです。どれも必要で、リクエストの毎回につき 送られます。

## Headers

`request_headers` の構造は次のようなものです : まずヘッダの数 `num_headers` がエンコードされます。 次にヘッダ名 `req_header_name` / 値 `req_header_value` の組が続きます。効率のため、一般的なヘッダは整数でエンコードして転送します。 ヘッダ名が基本ヘッダの一覧に無い場合は、通常通り (文字列として、長さ プレフィックス付きで) 転送されます。一般的なヘッダ `sc_req_header_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 のコードでは、最初の 2 バイト整数を取り込み、 目印になるバイト `'0xA0'` であれば、ヘッダ名の配列の インデックスを使います。先頭バイトが `0xA0` でない場合は、 先頭 2 バイトは文字列長を表す整数であると解釈し、読み込みはじめます。

ヘッダ名の長さは `0x9999 (==0xA000 -1)` 以上にならないという 仮定の下に動いていて、少しあいまいですが合理的な挙動になっています。

### 注:

`content-length` ヘッダはとても重要です。 存在していて非ゼロであれば、リクエストにはボディがある (例えば POST リクエスト) と推測し、そのボディを取り込むために 直後のパケットを入力ストリームから読み込みはじめます。

## 属性

`?` プレフィックスで始まる属性 (例 `?context`) は。省略可能です。それぞれ属性の型を示す 1 バイトのコードと、 値(文字列か整数)が続きます。 これらは順不同で送ることができます (C のコードは常に下の一覧順に 送るようですが) 。 オプションの属性のリストの最後には、特別な終了コードが送られます。 コードの一覧は :

Information | Code Value | Type Of 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 | Name (the name of the attribute follows)
?ssl_key_size | 0x0B | Integer |
are_done | 0xFF | - | request_terminator

`context` と `servlet_path` は現在の C の コードではセットされていません。また、ほとんどの Java のコードでも、 このフィールドで何が送られても無視されます (これらのコードの後に文字列が 送られると壊れるものもあります)。 これがバグなのか、単に未実装なのか、歴史的経緯で残っているコードなのか 分かりませんが、コネクションの両側ともで見当たりません。

`remote_user` と `auth_type` はおそらく HTTP レベルの認証を参照していて、リモートユーザのユーザ名と認証に使用した タイプ (例 Basic, Digest) についてやり取りします。

`query_string`, `ssl_cert`, `ssl_cipher`, `ssl_session` は HTTP と HTTPS の対応する部分を参照します。

`jvm_route` はスティッキーセッションのサポート―― ロードバランスしている複数のサーバ中の特定の Tomcat インスタンスと、 ユーザのセッションとを紐付ける機能――に使われます。

この基本属性一覧に無いものについては、`req_attribute` コード `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)

## 詳細 :

## Send Body Chunk

チャンクは基本的にはバイナリデータで、ブラウザに直接送られます。

## Send Headers

ステータスコードとメッセージが通常の HTTP の通信にはあります (例 `200` と `OK`)。レスポンスヘッダ名は、 リクエストヘッダ名と同様の方法でエンコードされます。 コードと文字列の判別方法の詳細に関しては、上記の header_encoding を参照してください。 一般的なヘッダのコードは :

名前 | コードの値
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

リクエスト処理サイクルの終了を知らせます。`reuse` フラグが真 `(==1)` の場合、現在使用している TCP コネクションは次の新しい リクエストに使えるようになります。`reuse` が偽 (C のコードでは 1 以外の全て) の場合は、コネクションを閉じることになります。

## Get Body Chunk

(ボディのサイズが大きすぎて最初のパケットに収まらない場合や、 リクエストがチャンク転送された場合などには、) コンテナはリクエストからの データ読み込み要求をします。サーバ側はそれに対して、最小 `request_length` 最大 `(8186 (8 Kbytes - 6))` の範囲で、未転送で残っているリクエストボディの大きさのデータを 送り返します。
ボディにそれ以上データが残っていない場合 (つまりサーブレットが ボディの最後を超えて読み込もうとした場合) 、サーバは ペイロード長 0 の空パケット`(0x12,0x34,0x00,0x00)` を送り返します。