AnyEvent::HTTP::CookiesではなくAnyEvent::HTTP::Cookies::Unit の話をしてきました #niigatapm
Niigata.pm tech talk #1では AnyEvent::HTTPで使ったCookie情報 あるいは他のクライントで使ったCookie情報を相互に再利用できたらいいよね っていう話をしてきました。
-
-
- -
-
外部WebAPIを利用してゴニョゴニョというのは、よくある(?)ケースだと思うけど、APIがないサイトは沢山あります。
その際、よく使われるのが ハンディブラウザ + スクレイピングツール で、WWW::Mechanize + Web::Scraper(Web::Query) の組み合わせが多いんじゃないでしょうか?
ただ、最近は AnyEvent 文脈で HTTP通信 を行うケースも多いと思います。
AnyEvent 文脈で WWW::Mechanize 相当の機能を持ったモジュールを僕は知りませんので、ログインなどセッション情報が必要なサイトではクッキーや他のヘッダー情報の管理は自前の処理機構上でやってます。
さて、比較的長い有効期限を持ったクッキーを使っているサイトもあります。その場合、実直にログインページからスタートする事なく、クッキーを使ってログイン状態のまま、目的のページへ移動することが可能な場合があります。
このCookie 情報をお気に入りのAnyEvent文脈、そうでない文脈で共有できると面白そうです。
Cookies情報の共有する際に
- AnyEvent::HTTP は単なるハッシュリファレンスでCookie情報をプールしてAnyEvent::HTTP自体に情報を操作させるのに対し、WWW::Mechanize は HTTP::Cookiesオブジェクトの内部で情報をプールし操作は HTTP::Cookiesのメソッドを使う
- 内部データの構造は似た構造
- キー&バリューの構造が異なる。AnyEvent::HTTP では ハッシュリファレンス。HTTP::Cookies では 配列リファレンス
- AnyEvent::HTTP は ネットスケープ準拠の Cookieに対応するのに対し、 HTTP::Cookies は Set-Cookies2 まで対応
- 他のクライアントで利用できる保存形式が必要
「他のクライント」を考えてみると
- WWW::Mechanize (isa LWP::UserAgent)
- 実際のブラウザ
- Ruby などの他の言語のクライアント
今回、HTTP::Cookies(::*) のsaveメソッドを使って保存する戦略を取ることにしました。2012.04.28現在
HTTP::Cookies の場合、デフォルトの保存形式は Set-Cookies3 で保存します。(サブクラスの場合は頃なる場合もあります。HTTP::Cookies::Safariでは plist形式のXML文書になります)
前置きが長くなりましたが本題
- AnyEvent::HTTP が扱った Cookie 情報を HTTP::Cookies にセットする関数
- (WWW::Mechanize, etc上で) HTTP::Cookies が扱った Cookie 情報を AnyEvent::HTTP で扱えるフォーマットに変換する関数
- AnyEvent::HTTP が扱った Cookie 情報を HTTP::Cookies の 内部で扱うフォーマットに変換する関数(使いどころがないけど)
の関数を提供するモジュール Ginger::Cookies を書いてみました(α版。細かいところは見なおさないといけないと)。
デモコード(一部ふせております)
#!/usr/bin/env perl use strict; use warnings; use HTTP::Cookies::Safari; use Ginger::Cookies qw(set_cookies anyevent_style); use AnyEvent; use AnyEvent::HTTP; use JSON; use YAML; my $WWW_PXXXX_NET = 'http://www.pxxxx.net'; my $stacc = "${WWW_PXXXX_NET}/stacc/my/home/all/all"; my $file = "$ENV{HOME}/Library/Cookies/Cookies.plist"; #my $file = 'Cookies.plist'; my $cookie_jar = HTTP::Cookies::Safari->new; $cookie_jar->load( $file ); my $jar = Ginger::Cookies::anyevent_style $cookie_jar; my $cv = AE::cv; http_request GET => $stacc, cookie_jar => $jar, sub { my($content, $hdr) = @_; if ($hdr->{Status} ne '200') { warn "failed: $hdr->{Status} $hdr->{Reason} $hdr->{URL}"; return $cv->send; } my $tt = ($content =~ /pxxxx\.context\.token\s=\s\'([^']+)\'/)[0]; my $login = ($content =~ /pxxxx\.user\.loggedIn\s=\s([^;]+);/)[0]; if ($login) { http_request GET => "${stacc}.json?tt=${tt}", cookie_jar => $jar, sub { my($content, $hdr) = @_; if ($hdr->{Status} ne '200') { warn "failed: $hdr->{Status} $hdr->{Reason} $hdr->{URL}"; return $cv->send; } my $table = decode_json $content; warn Dump $table; $cv->send; }; } }; $cv->recv; set_cookies $cookie_jar => $jar; $cookie_jar->save( $file );
オチ
質疑応答で「関数ではなくクラスで提供しては」と言う指摘があったので、実装方法考えます