はじめに |
このドキュメントは、ajp13として知られていた現在のApache JServプロトコルバージョン1.3の改良の提案です。
ここでは、プロトコルのすべてではなく、ajp13に追加する部分だけを説明します。
この提案は、tomcat-devリストからのコメントと、開発中に発見された誤りを含んでいます。
AJP13に欠けている機能 |
ajp13は、TomcatのようなServletエンジンとApacheのようなWebサーバを繋ぐのに適したプロトコルで、次のような特徴があります。
-
リクエストごとに再接続する時間をなくすために、永続的な接続を使用します。
-
ストリームサイズを減らすために、多くのHTTPコマンドをエンコードします。
-
WebサーバからServletエンジンに、(SSL認証のような)多くの情報を送信します。
しかし、ajp13は以下に関してサポートしていません。
-
WebサーバとServletエンジンの間のセキュリティ。
(ログイン機能がないので)誰でもajp13ポートに接続できます。
たとえば、telnetで接続して、何もデータを送信しなければ、リモートのスレッドを捉えておくことができます(接続にタイムアウトはありません)。
-
ServletエンジンからWebサーバに渡されるコンテキスト情報。
Webサーバコネクタであるmod_jkの設定には、どのURLを処理するかをWebサーバに指示するためのものがあります。
mod_jkのJkMount指示子は、WebサーバにどのURLをServletエンジンに転送しなければならないかを指示します。
Servletエンジンは、すでにどのURLを処理しなければならないかがわかっており、Tomcat 3.3には利用可能なコンテキストのリストからmod_jk用の設定ファイルを生成する機能がすでにあります。
-
ServletエンジンからWebサーバへのコンテキストの状態の更新。
ISPやバーチャルホスト提供サービスのような、多くのTomcatを使用する大規模サイトでは、
管理をおこなうためにあるコンテキストを停止する必要があるかもしれません。
このような場合、手前にあるWebサーバは、そのリクエストを他のTomcatに送るために、
そのコンテキストが現在ダウンしていることを知っていなければなりません。
-
リクエストを送信する前の接続状態の確認。
実際には、JKはリクエストをServletエンジンに送信してから、その応答を待ちます。
しかし、Socket APIの良い点の一つは、すでにクローズした接続に対して、
何もエラーをおこさずにwrite()するかもしれないのに、
すでにクローズした接続に対するread()はエラーコードを返すことなのです。
AJP13に対する追加 |
AJP13に追加されるかもしれない機能を説明します。
本稿は一つの提案にすぎないので、まずは、かなりごちゃごちゃだと思ってかまいません。
Tomcat MLでの議論は論点を明確にし、機能を追加するのには役立ちましたが、
それでも現在挙がっているのは「最低限必要」なものでしかないように思います。
-
高度なログイン機能
-
秘密鍵をWebサーバとServletエンジンで共有する基本的な認証システム、
-
将来的にAJP13に機能を追加する場合に、現在の実装の動作を保証するための、
基本的なプロトコルネゴシエーション。
-
「未知のパケット」の適正な処理。
-
WebサーバからServletエンジンへの渡される環境変数の拡張。
-
(SSL_KEY_SIZEのように)Servlet 2.3 API で必要な追加SSL情報。
高度なログイン機能 |
-
WEBサーバはLOGIN INIT CMD + NEGOCIATION DATA + WEB SERVER INFOを送信します。
-
TOMCATは、LOGIN SEED CMD + RANDOM DATAで返答します。
-
WEBサーバはRANDOM DATA+SECRET DATAのMD5を計算します。
-
WEBサーバは、LOGIN COMP CMD + MD5 (SECRET DATA + RANDOM DATA)を送信します。
-
TOMCATはLOGIN STATUS CMD + NEGOCIED DATA + SERVLET ENGINE INFOを返答します。
DOS攻撃を防ぐために、ServletエンジンはLOGIN CMDを15秒または30秒待ってから、
管理者が調査できるようにタイムアウト例外を報告します。
loginコマンドは、圧縮機能、暗号化、(起動時の)コンテキスト情報、
実行時のコンテキストの更新(起動/停止)、SSLの環境変数のレベル、
サポートしているAJPプロトコルレベル(level1/level2/level3・・・)
といった基本的なプロトコルネゴシエーション情報を含んでいます。
WEB SERVER INFOは、Webサーバ情報とコネクタ名(例, Apache 1.3.26 + mod_ssl
2.8.8 + mod_jk 1.2.1 + mod_perl 1.25)を含んでいます。
Servletエンジンは、ネゴシエーションマスクを(可能なものについては)それ自身のマスクで隠し、
それをログインが認められた時に返信します。
これによって、基本的なAJP13実装(level1)しかサポートしていないWebサーバでも、
より高度な(=level2/level3の)プロトコルハンドラをサポートしているServletエンジンと互いに連携して動作できるようになります。
AJP13は、コンパクトで高速になるように設計されているため、Webサーバにある多くのSSL情報はServletエンジンに転送されません。
そこで、より多くの情報を送れるように、クライアントのSSLデータ(証明書)、サーバのSSLデータ、
使用する暗号、その他のデータ(タイムアウトなど)という4つのネゴシエーションフラグを追加したいと思います。
メッセージストリーム |
+----------------+------------------+-----------------+
| LOGIN INIT CMD | NEGOCIATION DATA | WEB SERVER INFO |
+----------------+------------------+-----------------+
+----------------+----------------+
| LOGIN SEED CMD | MD5 of entropy |
+----------------+----------------+
+----------------+----------------------------+
| LOGIN COMP CMD | MD5 of RANDOM + SECRET KEY |
+----------------+----------------------------+
+-----------+---------------+---------------------+
| LOGOK CMD | NEGOCIED DATA | SERVLET ENGINE INFO |
+-----------+---------------+---------------------+
+------------+--------------+
| LOGNOK CMD | FAILURE CODE |
+------------+--------------+
-
LOGIN INIT CMD、LOGIN SEED CMD、LOGIN COMP CMD、LOGOK CMD、LOGNOK CMDの長さは1バイトです。
-
MD5、MD5 of RANDOM + SECRET KEYの長さは32文字(char)です。
-
NEGOCIATION DATA、NEGOCIED DATA、FAILURE CODEの長さは32ビットです。
-
WEB SERVER INFO、SERVLET ENGINE INFOはCStringです。
秘密鍵は、workers.propertiesの新しいプロパティsecretkeyで設定できます。
worker.ajp13.port=8009
worker.ajp13.host=localhost
worker.ajp13.type=ajp13
worker.ajp13.secretkey=myverysecretkey
シャットダウン機能 |
AJP13にはAJP12の機能であるシャットダウンコマンドがありません。
ログアウトで、Servletエンジンにシャットダウンを指示します。
+--------------+----------------------------+
| SHUTDOWN CMD | MD5 of RANDOM + SECRET KEY |
+--------------+----------------------------+
+------------+
| SHUTOK CMD |
+------------+
+-------------+--------------+
| SHUTNOK CMD | FAILURE CODE |
+-------------+--------------+
-
SHUTDOWN CMD、SHUTOK CMD、SHUTNOK CMDの長さは1バイトです。
-
RANDOM + SECRET KEYのMD5の長さは32文字(char)です。
-
FAILURE CODEの長さは32ビットです。
環境変数の拡張 |
注意:
JKでAJP13に関する作業をしていた時に、"JkEnvVar"を発見してしまいました。
以下の"環境変数の拡張"の記述内容は、現在の実装にすでにあるので、
AJP13の拡張時に実装されないかもしれません。
解説:
多くのユーザが、Webサーバのいくつかの環境変数をServletエンジンに渡したいようです。
ネットワークトラフィックを減らすために、
Webサーバは外部変数を示すテーブルを何らかの形で圧縮して送信することになります。
そこで、AJP13の既存の機能である、属性リストを使用したいと思います。
AJP13では次のような情報を送ることができます。
AJP13_FORWARD_REQUEST :=
prefix_code 2
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)
?context (byte string)
?servlet_path (byte string)
?remote_user (byte string)
?auth_type (byte string)
?query_string (byte string)
?jvm_route (byte string)
?ssl_cert (byte string)
?ssl_cipher (byte string)
?ssl_session (byte string)
?attributes *(attribute_name attribute_value)
request_terminator (byte)
圧縮した「Webサーバ属性名」を使用すれば、ネットワークトラフィックを減らすことができます。
+-------------------+---------------------------+-------------------------------+----+
| EXTENDED VARS CMD | WEB SERVER ATTRIBUTE NAME | SERVLET ENGINE ATTRIBUTE NAME | ES |
+-------------------+---------------------------+-------------------------------+----+
すなわち、
JkExtVars S1 SSL_CLIENT_V_START javax.servlet.request.ssl_start_cert_date
JkExtVars S2 SSL_CLIENT_V_END javax.servlet.request.ssl_end_cert_date
JkExtVars S3 SSL_SESSION_ID javax.servlet.request.ssl_session_id
+-------------------+----+-------------------------------------------+
| EXTENDED VARS CMD | S1 | javax.servlet.request.ssl_start_cert_date |
+-------------------+----+-------------------------------------------+
+----+-----------------------------------------+
| S2 | javax.servlet.request.ssl_end_cert_date |
+----+-----------------------------------------+
+----+-----------------------------------------+
| S3 | javax.servlet.request.ssl_end_cert_date |
+----+-----------------------------------------+
AJP13の拡張では、送信時に、S1, S2, S3という属性名と、それに対して2001/01/03, 2002/01/03,
0123AFE56といった属性値を使うことができます。
この例では、拡張されたSSL変数の使い方を示しましたが、
カスタマイズされた認証変数といった「個人的な」Webサーバの変数もServletエンジンで再使用できます。
それによるコストは、AJPのトラフィックが数バイト増えるだけです。
-
EXTENDED VARS CMDの長さは1バイトです。
-
WEB SERVER ATTRIBUTE NAME、SERVLET ENGINE ATTRIBUTE NAMEはCStringです。
-
ESは空のCStringです。
ServletエンジンからWebサーバに転送するコンテキスト情報 |
LOGIN PHASE終了後に、WebサーバはServletエンジンが処理するコンテキストとURLのリストを要求します。
これにより、多くのサイトでインストールが簡単になり、
tomcat-user MLで設定に関する質問が減り、Servlet API 2.3にも対応できるでしょう。
このモードは、新しいJkAutoMount指示子によって有効になるはずです。
例: JkAutoMount examples myworker1 /examples/
もしServletエンジンが処理するコンテキストをすべて取得したいなら、ワイルドカードが使えるでしょう。
例: JkAutoMount * myworker1 *
Servletエンジンは、/examples, /admin, /testのような多くのコンテキストを持てるようになるはずです。
あるワーカに対していくつかのコンテキストだけを使用したいかもしれません。
従来は、たとえばApache HTTPサーバでは、
Apacheのそれぞれの[virtual]領域内にJkMountを手動で設定することで実現していました。
また、Webサーバがバーチャルホストをサポートしている場合には、Servletエンジンがバーチャルホストのコンテキストだけを返すように、
バーチャルホストの情報も転送するようにしたいと思います。
この場合、Servletエンジンは、これらの特定の(servlet.xml内で定義された)
バーチャルサーバにマッチするURL/URIだけを返します。この機能は、ISP
や、負荷分散構成で多くのTomcatを使用している大規模サイトで役に立つと思われます。
+-----------------+-------------------+----------+----------+----+
| CONTEXT QRY CMD | VIRTUAL HOST NAME | CONTEXTA | CONTEXTB | ES |
+-----------------+-------------------+----------+----------+----+
+------------------+-------------------+----------+-------------------+----------+---------------+----+
| CONTEXT INFO CMD | VIRTUAL HOST NAME | CONTEXTA | URL1 URL2 URL3 ES | CONTEXTB | URL1 URL2 ... | ES |
+------------------+-------------------+----------+-------------------+----------+---------------+----+
context-queryコマンド経由で、コンテキストのリストに対して、
リモートのServletエンジンが処理するURL/MIMEのリストを取得する事ができます。(訳注: 原文では「リモート」の箇所はremoveとなっていますがremoteのtypoとみなして訳しています)
ワイルドカードモードでは、CONTEXTAは'*'のみとなります。
-
CONTEXT QRY CMDとCONTEXT INFO CMDの長さは1バイトです。
-
VIRTUAL HOST NAMEはCString、つまり最後がnullバイト(/0)で終了している文字の配列です。
-
空文字列はnullバイト(/0)だけです。
-
ESは空のCStringです。URI/URLリストの終わりもしくはCONTEXTリストの終わりを示します。
注意:
VirtualModeが使われていない時は、VIRTUAL HOST NAMEは'*'です。
この場合Servletエンジンは、処理するすべてのコンテキストを送信します。
ServletエンジンからWebサーバのコンテキスト情報の更新 |
コンテキストの更新は、コンテキストが有効化/無効化されるごとにServletエンジンから送信されるメッセージです。
この更新は、JkUpdateMount指示子を指定した時に使用可能になります。
この指示子で、AJP13_CONTEXT_UPDATE_NEGフラグを設定します。
例: JkUpdateMount myworker1
+--------------------+-------------------+----------+--------+----------+--------+----+
| CONTEXT UPDATE CMD | VIRTUAL HOST NAME | CONTEXTA | STATUS | CONTEXTB | STATUS | ES |
+--------------------+-------------------+----------+--------+----------+--------+----+
-
CONTEXT UPDATE CMD、STATUSの長さは1バイトです。
-
VIRTUAL HOST NAME、CONTEXTSはCStringです。
-
ESは空のCStringです。CONTEXTのリストの終わりを示します。
注意:
VirtualModeを使用しない時には、VIRTUAL HOST NAMEは'*'です。
STATUSの1バイトで、コンテキストがUP/DOWN/INVALIDであることを示します。
Servletエンジンへのコンテキストステータスの問い合わせ |
この問い合わせは、Webサーバが与えられたコンテキストがUPかDOWNまたはINVALID(削除すべき)かを決定するために使用します。
+-------------------+--------------------+----------+----------+----+
| CONTEXT STATE CMD | VIRTUAL HOST NAME | CONTEXTA | CONTEXTB | ES |
+-------------------+--------------------+----------+----------+----+
+-------------------------+-------------------+----------+--------+----------+--------+----+
| CONTEXT STATE REPLY CMD | VIRTUAL HOST NAME | CONTEXTA | STATUS | CONTEXTB | STATUS | ES |
+-------------------------+-------------------+----------+-------------------+--------+----+
-
CONTEXT STATE CMD、CONTEXT STATE REPLY CMD、STATUSの長さは1バイトです。
-
VIRTUAL HOST NAME、CONTEXTはCStringです。
-
ESは空のCStringです。
注意:
VirtualModeが使用されていない時は、VIRTUAL HOST NAMEは空文字列です。
未知のパケットの処理 |
良く設計されたプロトコルであっても、片側(WebサーバまたはServletエンジン)が、
理解できないメッセージを受け取ることがあるかもしれません。
このような場合に、受信側は処理できなかったメッセージを付加して'UNKNOWN PACKET CMD'を送信します。
+--------------------+------------------------+-------------------+
| UNKNOWN PACKET CMD | UNHANDLED MESSAGE SIZE | UNHANDLED MESSAGE |
+--------------------+------------------------+-------------------+
メッセージによっては、送信側はエラーを報告し、
可能な場合にはそのメッセージを受信側に転送しようとします。
-
UNKNOWN PACKET CMDの長さは1バイトです。
-
UNHANDLED MESSAGE SIZEの長さは16ビットです。
-
UNHANDLED MESSAGEはバイトの配列です。(配列の長さはUNHANDLED MESSAGE SIZEに含まれます)
注意:
UNHANDLED MESSAGE SIZEが追加されています(開発中)。
リクエストを送信する前の接続の確認 |
注意: この機能は使われないかもしれません。Webサーバ側において、リクエストの転送前に余分なI/O(読み込み)が必要になり、普通のプロセスが遅くなるためです・・・。
Socket APIの良い点の一つは、半分クローズしたソケットに書き込むことができることです。
Servletエンジンがソケットをクローズした時に、Webサーバはそのソケットに次のread()をおこなった時にだけ、クローズを検出できます。
基本的に、AJP13プロトコルでは、WebサーバはHTTP HEADERとHTTP BODY(POSTでは8Kまで)をServletエンジンに送信して、
それから返答を受信しようとします。
そのコネクションがクローズされた場合は、Webサーバはそれを受信時にしか知ることはできません。
私たちはバッファリング手法を使用することができますが、
8Kを超えるデータのアップロード操作にServletエンジンを使用した時には、何が起るかわかりません。
それに対するAJP13プロトコルの改変案は、そのサービスの終了後に数バイトの読み込みを追加することです。
WebサーバとServletエンジン間のやりとりの例
AJP HTTP-HEADER (+ HTTP-POST) (WEB->SERVLET)
AJP HTTP-REPLY (SERVLET->WEB)
AJP END OF DISCUSSION (SERVLET->WEB)
---> AJP STATUS (SERVLET->WEB AJP13)
AJP STATUSは、Servletエンジンはn番目のリクエスト/レスポンスの終了時に読み込まれずに、
次のセッションの最初に読み込まれます。
この時に、Webサーバはまだ読み込むデータがあるかどうかを判断するために、
OS依存の機能(APR機能だとベターです)を使用することもできます。そして、
データはコンテキストの更新かもしれません。
これによって、Webサーバがリクエストを無効化されたコンテキストに送信することを避けることができます。
この場合に負荷分散が行われていると、
そのリクエストを処理する別のServletエンジンが検索されます。
この機能は、ISPや、多数のTomcatを使用する大規模サイトで、
サービスを中断せずにServletエンジンを更新するのに役立ちます。
+------------+-------------+
| STATUS CMD | STATUS DATA |
+------------+-------------+
-
STATUS CMDとSTATUS DATAの長さは1バイトです。
おわりに |
拡張されたAJP13プロトコルの目的は、元のAJP13の制約のいくつかを克服することです。
これには、より簡単な設定、大規模サイトやTomcatの集合に対するよりよりサポート、
簡単な認証システムとプロトコルのバージョンアップへの準備などがあります。
JK(ネイティブ)とServletエンジン (Java) でajp13の安定した実装を使用するので、
よく知られているajp13の無理のない改良となります。
拡張されたAJP13のコマンドとIDのインデックス |
AJP13プロトコルに追加されたコマンドとIDのインデックス
コマンドのID |
| コマンド名 |
コマンド番号 |
| AJP13_LOGINIT_CMD |
0x10 |
| AJP13_LOGSEED_CMD |
0x11 |
| AJP13_LOGCOMP_CMD |
0x12 |
| AJP13_LOGOK_CMD |
0x13 |
| AJP13_LOGNOK_CMD |
0x14 |
| AJP13_CONTEXT_QRY_CMD |
0x15 |
| AJP13_CONTEXT_INFO_CMD |
0x16 |
| AJP13_CONTEXT_UPDATE_CMD |
0x17 |
| AJP13_STATUS_CMD |
0x18 |
| AJP13_SHUTDOWN_CMD |
0x19 |
| AJP13_SHUTOK_CMD |
0x1A |
| AJP13_SHUTNOK_CMD |
0x1B |
| AJP13_CONTEXT_STATE_CMD |
0x1C |
| AJP13_CONTEXT_STATE_REP_CMD |
0x1D |
| AJP13_UNKNOW_PACKET_CMD |
0x1E |
ネゴシエーションフラグ |
| コマンド名 |
番号 |
説明 |
| AJP13_CONTEXT_INFO_NEG |
0x80000000 |
Webサーバがログイン後にコンテキスト情報を要求 |
| AJP13_CONTEXT_UPDATE_NEG |
0x40000000 |
Webサーバがコンテキストの更新を要求 |
| AJP13_GZIP_STREAM_NEG |
0x20000000 |
Webサーバが圧縮ストリームを要求 |
| AJP13_DES56_STREAM_NEG |
0x10000000 |
Webサーバが秘密鍵を使ったDES56暗号ストリームを要求 |
| AJP13_SSL_VSERVER_NEG |
0x08000000 |
サーバSSL変数の拡張情報 |
| AJP13_SSL_VCLIENT_NEG |
0x04000000 |
クライアントSSL変数の拡張情報 |
| AJP13_SSL_VCRYPTO_NEG |
0x02000000 |
暗号SSL変数の拡張情報 |
| AJP13_SSL_VMISC_NEG |
0x01000000 |
その他のSSL変数の拡張情報 |
| ネゴシエーションID |
番号 |
説明 |
| AJP13_PROTO_SUPPORT_AJPXX_NEG |
0x00FF0000 |
サポートしているプロトコルのマスク |
| AJP13_PROTO_SUPPORT_AJP13L1_NEG |
0x00010000 |
AJP13 Level 1をサポート |
| AJP13_PROTO_SUPPORT_AJP13L2_NEG |
0x00020000 |
AJP13 Level 2をサポート |
| AJP13_PROTO_SUPPORT_AJP13L3_NEG |
0x00040000 |
AJP13 Level 3をサポート |
他のすべてのフラグは将来の使用のために予約されているので、0に設定しなければいけません。
エラーID |
| エラーID |
番号 |
| AJP13_BAD_KEY_ERR |
0xFFFFFFFF |
| AJP13_ENGINE_DOWN_ERR |
0xFFFFFFFE |
| AJP13_RETRY_LATER_ERR |
0xFFFFFFFD |
| AJP13_SHUT_AUTHOR_FAILED_ERR |
0xFFFFFFFC |
ステータス |
| ステータスID |
番号 |
| AJP13_CONTEXT_DOWN |
0x01 |
| AJP13_CONTEXT_UP |
0x02 |
| AJP13_CONTEXT_OK |
0x03 |
|