理系学生日記

おまえはいつまで学生気分なのか

WebService::Simple の POST でハマる

WebService::Simple(v0.16) で POST するときにハマってしまったことがあります。

前提

WebService::Simple は、コンストラクタにパラメータを設定しておけば、後段で get や post を呼び出すときにそのパラメータを自動的に付加してくれる便利機能があります。
例えば、WebService::ReadItLater を WebService::Simple を継承する形で作るときに、コンストラクタで以下のように設定すれば、以後のリクエストにおいて apikey などを明示的に付加する必要はありません。

# in WebService::ReadItLater
    my $self = $class->SUPER::new(
        params => { 
            apikey   => $apikey,
            username => $user,
            password => $pass,
        },
        %$args,
    );

問題

問題というのは、上記のように設定した項目が、POST をするときは BODY ではなく、HTTP ヘッダにあらわれてしまうことです。下記は HTTP::Request を Dumper でダンプしたときの内容。password や username がヘッダに表われてしまっています。

          '_headers' => bless( {
                                 'user-agent' => 'libwww-perl/5.831',
                                 'hash(0x10094acb8)' => undef,
                                 'content-type' => 'application/x-www-form-urlencoded',
                                 'password' => 'password'
                                 'content-length' => 0,
                                 'apikey' => 'hogehoge'
                                 'username' => 'fugafuga'
                               }, 'HTTP::Headers' ),

原因

WebService::Simple は LWP::UserAgent の子クラスの形で実装されていて、POST するときはもちろん LWP::UserAgent::post を呼び出しています。LWP::UserAgent::post には @params をパラメータとして渡していますが、コメントに記載のある通り、@params の最初あたりには「前提」欄で触れたパラメータが設定されます。

# WebService::Simple
    # default parameters must come *before* @params, so unshift instead
    # of push
    unshift @params, %{ $self->basic_params };
    my $response = $self->SUPER::post( $uri, @params );
}

ただ、この場合 LWP::UserAgent::post は、HTTP BODY ではなくヘッダに情報を付加してしまいます(実際には HTTP::Request::Common がリクエストを組み立ててる)。
そのため、WebService::Simple の子クラスで

    $self->post( 'send' => { read => $json } );

とした場合、{ read => $json } は HTTP BODY ではなく、HTTP ヘッダの方に挿入されてしまう。

解決策

これで良いのかしら。という感じのパッチを書いた。
たぶんこれで、get と同じ形で post が呼べるはず。

--- Simple.pm.orig	2009-06-13 11:15:18.000000000 +0900
+++ Simple.pm	2009-12-13 20:14:45.000000000 +0900
@@ -173,7 +173,19 @@
 }
 
 sub post {
-    my ( $self, $url, @params ) = @_;
+    my $self = shift;
+    my ( $url, @params );
+
+    if ( ref $_[0] eq 'HASH' ) {
+        $url   = "";
+        @params = %{ shift @_ };
+    }
+    else {
+        $url = shift @_;
+        if ( ref $_[0] eq 'HASH' ) {
+            @params = %{ shift @_ };
+        }
+    }
 
     # XXX - do not include params
     my $uri = $self->request_url(
@@ -181,10 +193,11 @@
         extra_path => $url
     );
 
+    my @headers = @_;
     # default parameters must come *before* @params, so unshift instead
     # of push
     unshift @params, %{ $self->basic_params };
-    my $response = $self->SUPER::post( $uri, @params );
+    my $response = $self->SUPER::post( $uri, {@params}, @headers );
 
     if ( !$response->is_success ) {
         Carp::croak( "request to $url failed: " . $response->status_line );