|
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
この文書の元となったのは、2000 年 12 月に Dan Milstein ( danmil@shore.net)によって書かれたものです。 現在公開されている文書は XML ファイルから生成され、 よりいっそう Tomcat の文書と統合しやすいものになっています。 この文書では、Apache JServプロトコル バージョン1.3 (以後は ajp13 )について説明します。 現在、プロトコルの動作方法についての文書はまったく存在しません。 この文書は、mod_jkの保守をおこなう人たちや、プロトコルを他に(たとえば、Jakarta 4.xに)移植したい人たちがより楽に作業できるように、この問題を改善しようとする試みです。
私は、このプロトコルの設計者の一人ではありません - Gal Shachorがオリジナルの設計者だったと思います。 この文書のすべての情報は、私がTomcat 3.xのコードで見つけた実際の実装から得ています。 この文書が役に立つものであればと思いますが、完全に正確かどうかについては保証できません。 また、設計方針の決定の理由についても私は知りません。 私ができることは、いくつかの(設計での)選択に対して可能性のある理由づけを行うことだけですが、それらは単なる推測にすぎません。 全般的に、Shachorが書いたC言語のコードは非常にきれいで、(ほとんど文書化されていないにせよ)理解しやすいものです。 Java言語のコードについては、私が書き直して、かなり読みやすくなったと思います。
Gal Shachorがjakarta-devメーリングリストに投稿した電子メールによると、 JK (と ajp13 )の最初の目標は、 mod_jserv と ajp12 を以下のように拡張することでした(私は、WebサーバとServletコンテナ間の通信に関連した目標を取り込んだだけです)。
ajp13 プロトコルは、パケット指向です。 読みやすいプレーンテキスト形式ではなく、バイナリ形式を選択したのは、おそらく性能上の理由からだと思われます。 Webサーバは、ServletコンテナとTCPコネクション上で通信します。 コストのかかるソケット作成プロセスを減らすために、WebサーバはServletコンテナに対して永続的にTCPコネクションを保持して、複数のリクエスト/レスポンスのサイクルでコネクションを再利用しようとします。 いったんある特定のリクエストにコネクションを割り当てると、リクエスト処理サイクルが終了するまでは他に使用することはありません。 つまり、リクエストは、コネクション上で多重化されることはありません。 これによって、コネクションの両側のコードをより単純にできる反面、結果として、一度にオープンしているコネクション数が多くなってしまいます。 いったんWebサーバがServletコンテナに対するコネクションをオープンすると、コネクションは以下の状態のいずれかになります。
いったんコネクションがある特定のリクエストを処理するために割り当てられると、基本的なリクエスト情報 (たとえば、HTTPヘッダなど)は、コネクション上を高度に圧縮された形式(たとえば、一般的な文字列は整数にエンコードされます)で送信されます。 このフォーマットの詳細については、この後のリクエストパケット構造で説明します。 リクエストに対してボディ部が存在する(content-length > 0 である)場合には、直ちに別のパケットで送信されます。 この時点では、おそらくServletコンテナはリクエスト処理を開始する用意ができています。 その場合には、以下のメッセージをWebサーバに返信することができます。
各メッセージは、異なるフォーマットのデータパケットで実現します。 詳しくは、以下で述べるレスポンスパケット構造を参照してください。
このプロトコルは、XDRの特徴を少し受け継いでいますが、多くの点で異なっています(たとえば、4バイトのアライメントがないことです)。 バイトオーダー: 私はバイトのエンディアンがどうなっているかはよくわかりません。 リトルエンディアンだと思うのですが、それはXDRがそうだからです。 そしておそらく(C言語側のコードで)sys/socketライブラリが自動的に(バイトオーダーに関して)処理してくれているのではないかと思います。 ソケット呼び出しについて誰かもっと詳しく知っている人が参加してくれればすばらしいと思います。 このプロトコルには、byte, boolean, integer, stringという4つのデータ型があります。
多くのコードによると、最大パケットサイズは8 * 1024 バイト (8K) です。 パケットの実際の長さは、ヘッダ部にエンコードされます。
サーバからコンテナに送信されるパケットは、0x1234で始まります。 コンテナからサーバに送信されるパケットは、ABで始まります (つまり、ASCIIコードのAの直後にASCIIコードのBがきます)。 この最初の2バイトの直後に、送信データの長さの整数がきます(この前に説明したようにエンコードされています)。 つまり、論理的な最大データサイズは2^16ですが、実際の最大値は8Kに制限されています。
大部分のパケットでは、送信データの最初の1バイトにメッセージのタイプがエンコードされています。 この例外は、サーバからコンテナに送信されるリクエスト内容のパケットです - これらは標準パケットヘッダ (0x1234の後にパケット長)を付加して送信されますが、その直後にプレフィクスコードはありません(これは私は間違いのように思います)。 Webサーバは、以下のメッセージをServletコンテナに送信することができます。
Servletコンテナは、Webサーバに以下のタイプのメッセージを送信することができます。
上記のメッセージは、それぞれ別の内部構造を持っていますので、この後に説明します。
サーバからコンテナに送信される、タイプ"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) [この解析方法については、この後を参照してください]
sc_req_header_name := 0xA0xx (integer)
req_header_value := (string)
attributesはオプションで、以下のような構造です。
attribute_name := (string) attribute_value := (string) "content-length"は極めて重要なヘッダであることに注意する必要があります。なぜなら、このヘッダはコンテナが次のパケットをすぐに探すかどうかを決めるからです。 転送するリクエストの要素の詳細を以下に示します。
すべてのリクエストで、この値は2になります。 詳しくは上記の プレフィックスコード を参照してください。
HTTPメソッドを、以下のように1バイトにエンコードしています。
これらについては、名前を見れば簡単にわかるでしょう。 これらはすべて必須であり、リクエストごとに送信されます。
request_headersの構造は以下の通りです。 最初に、ヘッダの数(num_headers)がエンコードされます。 次に、ヘッダ名(req_header_name)と値(req_header_value)の組の集合が続きます。 一般的なヘッダ名は、容量を節約するために整数としてエンコードします。 ヘッダ名が基本ヘッダ(=一般的なヘッダ)のリストにない場合には、普通に(最初に長さがついた文字列として)エンコードされます。 一般的なヘッダ(sc_req_header_name)のリストとそのコードは、以下に示します(大文字・小文字を区別します)。
これを読み込むJava言語のコードでは、まず最初の2バイトの整数を読み込んで、 MSB (Most Significant Byte)が'0xA0'の場合には、 2番目のバイトをヘッダ名の配列に対するインデックスである整数と見なします。 最初の1バイトが'0xA0'でない場合には、2バイトの整数が文字列の長さを表していると見なして、 それを読み込みます。 これは、0x9999 (==0xA000 - 1)より長いヘッダ名が存在しないことを仮定すれば動作しますが、これはやや独断的ですが、きわめて妥当でしょう。(もし、あなたが私のようにcookieの仕様と、どのくらいの長さのヘッダを得ることができるかについて考え始めたとしても、恐れることはありません - というのは、これはヘッダの 名前 の制限であって、ヘッダの 値 の制限ではないからです。 管理できないくらい巨大なヘッダ名についてHTTP仕様が定義されることは、とてもありえません。) (訳注: 0x9999 は、0x9FFF の間違いだと思われる。) 注意: content-lengthヘッダは極めて重要です。 このヘッダが存在して、0以外の値をとる場合には、コンテナはリクエストに(たとえば、POSTリクエストのように)ボディ部があるものとして、ただちにボディ部を取得するために別のパケットをインプットストリームから読み込みます。
?が先頭についた属性(例 ?context)のリストのすべては必須ではありません。 それぞれについて、属性のタイプを示す1バイトコードが定義されていて、文字列をこの値に変換します。 これらのヘッダは、(C言語のコードは常に以下の順序で送信するのですが)任意の順序で送信できます。 必須ではない属性のリストの最後を知らせるために、特別な終了コードを送信します。 バイトコードのリストは以下の通りです。
contextとservlet_pathは、現在はC言語のコードでは設定されませんし、 Java言語のコードの大部分では、これらのフィールドが送信されても完全に無視されます (そして、これらのコードの後に文字列が送信された場合には、実際に失敗します)。 私はこれがバグなのか、未実装の仕様なのか、それとも単に昔のコードの痕跡なのかは知りませんが、 コネクションの両側で実装されていません。 remote_userとauth_typeは、どうやらHTTPレベルの認証に対応しているらしく、 リモートのユーザのユーザ名とその本人確認をおこなうために使用した認証のタイプ(例:BASIC認証、ダイジェスト認証)のようです。 なぜパスワードが一緒に送信されないのかは、よくわかりませんが、私はHTTP認証についてはまったくわからないのです。 query_stringとssl_cert、 ssl_cipher、ssl_sessionは、HTTPとHTTPSに相当する部分に関するものです。 jvm_routeは、私が理解している限りでは、スティッキ・セッションをサポートするために - つまり複数のサーバで負荷分散している時に、ユーザのセッションとある特定のTomcatインスタンスを関連付けるために使用しているようです。 私は詳しいことについては知りません。 この基本属性のリスト以外にも、他の多くの属性をreq_attributeコード (0x0A)を用いて送信します。 属性名と値を表す文字列のペアは、このコードのすぐ後に送信されます。 環境変数は、このメソッドを用いて渡します。 最後に、すべての属性を送信した後に、属性のターミネータとして、0xFFを送信します。 これは、属性のリストの終了とともに、リクエストパケット全体の終了を知らせます。 サーバは、shutdownパケットも送信することができます。 基本的なセキュリティを保証するために、実際にはコンテナはリクエストが運用しているマシンと同じマシンから送信されてくる場合にのみ、終了します。
コンテナがサーバに返信することができるメッセージは以下の通りです。
AJP13_SEND_BODY_CHUNK :=
prefix_code 3
chunk_length (integer)
chunk *(byte)
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) [この解析方法については、この後を参照してください]
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")。 レスポンスヘッダ名は、リクエストヘッダ名と同じ方法でエンコードします。 コードと文字列を区別する方法についての詳しい説明は、 上記 を参照してください。 一般的なヘッダのコードは、以下の通りです。
コードや文字列のヘッダ名のすぐ後に、ヘッダの値がエンコードされます。
このリクエスト処理サイクルの終了を知らせます。 reuseフラグがtrue (==1)の場合には、このTCPコネクションを新しく到着するリクエストを処理するために使用できます。 reuseがfalse (実際のC言語のコードでは1以外の値です)の場合には、 このコネクションをクローズしなければいけません。
(リクエストのボディ部が非常に大きくて、送信された最初のパケット内に収まらなかった場合に)コンテナがリクエストからの追加データを要求します。
サーバは、ボディ部のパケットを、ある程度のデータと一緒に返信します。その中には少なくとも、request_lengthと、最大送信ボディサイズ (XXX)、それにリクエストボディからまだ送信されていないバイト数が含まれています。
リクエストヘッダの合計サイズが最大パケットサイズを越えた時に、何が起こるでしょうか? 8K以上の場合には、リクエストヘッダの二番目のパケットを送信する準備がされていません(確かめていませんが、私はレスポンスヘッダについてはうまく処理できると思います)。 私は、リクエストヘッダの初期集合に入っている8K以上のデータを取得する方法が存在するかどうかは知りませんが、おそらく存在するでしょう(長いSSL情報を持った長いCookieと多量の環境変数を組み合わせれば、簡単に8Kを越えるでしょう)。 私は、このような場合にヘッダが送信できるかを試す前に、コネクタが落ちるのではないかと思いますが、確かめたわけではありません。 認証についてはどうなのでしょうか? Webサーバとコンテナの間でコネクションの認証が行われてないように思われます。 これについては、私は潜在的な危険を感じてます。 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
[訳注: これは鰈崎 義之が翻訳しました。日本語訳に対するコメントがあれば、こちらに送って下さい。(風間氏翻訳のAJPv13.htmlをベースに一部追加・修正)]
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||