Apache module APIでリクエストボディを取り出す場合は関数ap_get_client_blockを使用して、クライアントとのネットワークバッファからコンテンツを取り出します。このバッファの根本はSocketなので、一度読み出したデータはこのバッファから取り出すことはできなくなる。
二度読みしたくなるケース
例えばユーザがHTTP POSTでアップロードしたファイルのウィルスチェック等をリアルタイムに行いたい場合、通常はチェックするハンドラと保存するハンドラを別々に書くだろう。または保存用の汎用的なハンドラを使い回す前提で、何らかのフィルタを行うハンドラを実装したい場合などなど。手っ取り早いのは適当な記憶領域を準備して、チェック要に読み込んだリクエストボディを保存しておき、あとに構えた保存用のハンドラはその領域のデータを保存する構成。しかし独自の領域を参照させるとなると、通常のap_get_client_blockを使った読み取り方ができなくなるので、汎用性が失われてしまいます。
ならば普通に読めるように
ap_get_clinet_blockはクライアントとのsocketを抽象化したBUFF構造体へのポインタを介してデータを取得している。このBUFF構造体はsocketだけではなくメモリ領域やファイル等も同じI/Fで取り扱うことができる。ならば一度socketから読み込んだリクエストボディをメモリもしくはファイルに保存し、BUFF構造体にセットすれば2回以上ap_get_client_blockで読み出すことができるようになる。テンポラリファイルを介する
リクエストボディをテンポラリファイルに保存し、そのファイルディスクリプタをr->connection->client->fd_inにセットする。ボディの長さはremainingフィールドにセットし、読み取り済みの長さを保持するread_lengthフィールドは0にリセットする。static int rewind_client_block(request_rec *r, char *body, size_t len) { char tmpfile[] = "/tmp/rewind_client_block.XXXXXX"; int fd; fd = mkstemp(tmpfile); if (fd == -1) return FORBIDDEN; unlink(tmpfile); write(fd, body, len); lseek(fd, 0, SEEK_SET); r->connection->client->fd_in = fd; r->remaining = len; r->read_length = 0; return OK; }
メモリを介する
リクエストボディを保持するバッファのポインタをr->connection->client->inptrにセットし、ボディの長さはr->connection->client->incntとr->remainingにセットする。read_lengthフィールドを0にリセットするのを忘れずに。static int rewind_client_block(request_rec *r, char *body, size_t len) { r->connection->clinet->inptr = body; r->connection->client->incnt = len; r->remaining = len; r->read_length = 0; return OK; }
使い方
リクエストボディのサイズに応じてオンメモリで処理するか、ファイルに落とすか判断させるなど併用した方が良いと思う。運用するホストのメモリやストレージによって、メモリかファイルかどちらにするかは変わってくるはずなので。というわけで、こうしておけば
俺様のホストにはMP3なんかアプさせねーぜなんてことが簡単(??)にできるようになります。fixupsフェーズ等でリクエストボディをチェックして、問題なければcgi-scriptハンドラで普通に処理させる、といったことができるので。おそらくfixupsフェーズ以前で処理する限りはCGIだろうがPHPだろうがJava Servletだろうが同じじゃないかしらん。