CGI.pm の POSTDATA

CGI.pmでtext/xmlなPOSTデータを読みたい場合、

my $q = CGI->new;
$q->param('POSTDATA')

で読める。いままで知りませんでした。

CGI.pm で $q->param('POSTDATA') とすると、POST で送られて来たデータの body をそのまま取り出せる、という話。

どういうときにこの API が役に立つかというと、かぜぶろさんのタイトルにある通り XMLAPI をサーバー側に実装したいとき。

もともと CGI.pm は通常のウェブリクエストを処理するために考えられているので、POST のデータを標準入力から読み込み、それを parse して $q->param('foobar') と GET のときと同じ API でアクセスできるようにしてます。が、AtomPPXML-RPC なんかの場合、その POST のデータが name=value&name=value... ではなく XML になってるので、CGI.pm の parse ロジックをすっとばしてそのままごっそり持ってくる必要が出てきます。で、POSTDATA でそれを取得すると。

ただ、CGI.pm のソースを読むと分かるとおり POSTDATA で生データがとれるケースというのは結構限定的で、Request Method が POST 且つ Content-Type がフォームから送られてきてる以外のとき、となってます。そうすると、XML-RPC のときとかはいいけど、PUT を使う AtomPP を実装するときに困ったりします。AtomPP の PUT は CRUD の U (Update) に対応するので、POST の時同様クライアントからデータをもらわないといけない、でも CGI.pm の API ではそれがとれない、という罠があります。

結局、

## CGI の POSTDATA は PUT では使えないので直接読む
my $len = $ENV{CONTENT_LENGTH} || 0;
read STDIN, $self->{request_content}, $len;
unless ($self->{request_content}) {
    return $self->error(500, 'Atom feed body is required.');
}
1;

みたいなことをやるはめになってしまいます。CGI.pm の POSTDATA がある周辺がメソッドになってれば、それをオーバーライドするなりっていう方法があると思うんですが。

ちょっと話がずれるのですが、先日の Web APIフレームワークのところも絡めると、Web API が簡単にハンドリングできるフレームワークは、リクエストがどんな形をしているかを断定的に実装してはいけない気がします。

つまりは、CGI.pm のようなものと密結合するのはなく、フレームワーク独自の Request クラスを作って、それが CGI.pm や Apache::Request などのラッパになっていると。そのラッパが、実際に利用するリクエストクラスをいじってもう少し細かい API を持っているような感じ。

CatalystCatalyst::Request でその抽象化をしていて、$c->req->body で生データが取り出せるようになってますね。Catalyst::Request のインスタンスは各エンジンの初期化処理の中で prepqre_request により組み立てられます。Builder パターンみたいな具合。mod_perl とか server.pl とか FastCGI とか、動作環境によってリクエストの実装が変わってくるのを想定してこういう作りになってるんだと思いますが、結果として Catalyst::Request を賢くすれば、Web API を実装するときに必要な APIフレームワークが用意することができて幸せです。

Sledge とか Rails もこんな感じでリクエストが抽象化されてますよね、確か。