このエントリでは、(WWW::Mechanize ではなく)LWP::UserAgent を使って Livedoor Reader にログインするにはどうすれば良いのかを説明します。
WWW::Mechanize を使った方法
Livedoor Reader は(未公開ながらも)API を持っているので、これが利用できると世界が広がるのですが、当該の API を使用するためにはログインが必要です。
これはなぜかというと、API を使用するためには、ログイン時に発行される API Key を POST/GET のパラメータとして送らなければならないからです。
では、このログインをどうすれば良いのかですが、Livedoor Reader では単にログイン用フォームに POST で id/pass を送りつけるだけではログインができません。
現在もっとも使用・確立されている方法は、miyagawa さんの Plagger::Plugin::Subscription::LivedoorReader で使用されている、以下の方法だと思います。
少々割愛したソースもありますが、WWW::Mechanize を使用して http://reader.livedoor.com/reader/ にアクセスした後、ログイン用フォームに id/pass をセットして POST します。
local $^W; # input type="search" warning $self->{mech}->get("http://reader.livedoor.com/reader/"); if ($self->{mech}->content =~ /name="loginForm"/) { Plagger->context->log(debug => "Logging in to Livedoor Reader"); $self->{mech}->submit_form( form_name => 'loginForm', fields => { livedoor_id => $self->conf->{username}, password => $self->conf->{password}, }, ); if ( $self->{mech}->content =~ /class="headcopy"/ ) { Plagger->context->error("Failed to login using username & password"); } } $self->{mech}->cookie_jar->scan( sub { my($key, $val) = @_[1,2]; if ($key =~ /_sid/) { $self->{apikey} = $val; return; } }, ); }
LWP::UserAgent を使いたい理由
Web サービスに対するクライアントモジュールを書く場合、どうやってテストケースを実装するかが問題になります。
実際の Web サービスをテストに使うという方法はもちろんありますが、基本的に使用すべきでないのは周知の事実です。
開発時点ならいくらかのアクセスで済むかもしれませんが、例えばこのモジュールが CPAN にアップロードされ、CPAN testers で実行されたらどうなるでしょうか。多数のサーバでテストが走り、DDoS のような状況が発生するかもしれません(まあそれほど多数ではないかもしれないけど)。対象が大きいサイトなら、なんともないかもしれませんが、数に関わらず、はっきりいってこれはやめたほうがいいですね。先方に迷惑がかかります。
HTTP通信を含むモジュールのテスト - Articles Advent Calendar 2011 Test
ここで CPAN を見渡すと、Test::LWP::UserAgent というモジュールを使えば簡単に LWP::UserAgent の Mock が作れそうです。
クライアントモジュールのコンストラクタで通信に使用する UserAgent を切り替えられるようにしておきさえすれば、ユニットテストを本番サービスを使用することなく記述することができそうです。
というわけで、Livedoor Reader 用クライアントを書き、そのテストを記述するためには、LWP::UserAgent を使いたい。
# クライアントモジュールのコンストラクタ例 sub new { my ($klass, %param) = @_; my $ua = delete $param{ua} || LWP::UserAgent->new( #hogehoge ); return bless { ua => $ua, } => $class; } # テスト my $ua = Test::LWP::UserAgent->new( #hogehoge ); $ua->map_response( qr[ #なんかの URL ] => HTTP::Response->new(hogehoge) ); my $client = WebService::Hogehoge->new( ua => $ua );
LWP::UserAgent を使ったログイン方法
ポイントは単純です。
- LWP::UserAgent で cookie を使用できるようにする
- POST メソッドでもリダイレクトを有効化する
Cookie の使用を許可するのは、Livedoor Reader 側が、ログイン時に id/pass だけでなく Cookie も参照している(と思われる)ため。
POST メソッドでもリダイレクトを有効化するのは、ログイン成功時に Livedoor Reader から HTTP 302 を返却されてくるためです*1。
エラー処理等を割愛すると、実際のコードは以下のような感じになります。
use LWP::UserAgent; # Cookie を有効化した UserAgent インスタンスを作成 my $ua = LWP::UserAgent->new( cookie_jar => {} ); # Cookie を取得 my $content = $ua->get( 'http://reader.livedoor.com/reader/' )->content; # 隠しパラメータの token 値を取得 my ($token) = $content =~ /"_token"\s+value="(\w+)"/; # id, pass を使用して POST $ua->post('https://member.livedoor.com/login/index', { _token => $token, '.next' => 'http://reader.livedoor.com/reader/', '.sv' => 'reader', livedoor_id => 'your id', password => 'your pass', }); # api key を取得 my $apikey; $ua->cookie_jar->scan( sub { my($key, $val) = @_[1,2]; if ($key =~ /_sid/) { $apikey = $val; return; } });
*1:実際には、不成功時も 302 だったりしますが