Perl モジュール。 LWP::UserAgent のサブクラス。
ページをキャッシュする機能を持つ WWW::Mechanize のサブクラスとして WWW::Mechanize::Cached がある。
WiKicker や DiKicker でうまく他のサイトの情報を取り込んで利用できるようにしたい。 相手サイト・自サイトともに負荷をかけないように処理するには、うまくキャッシングする必要がある。
キャッシュ機能のあるPerl用HTTPユーザエージェントには
などがある。
WWW::Mechanize::Cached は1度取得したレスポンスを無条件に1日間キャッシュする。 WWW::Mechanize のサブクラスで、便利な機能が利用できるが、キャッシュは適当。
キャッシュは Cache::FileCache決め打ち。
LWP::UserAgentのサブクラス。 Expires、Last-Modified、Etag ヘッダを考慮して処理する。
キャッシュは Cache::FileCache決め打ち。
fetch サブルーチンのみを提供するシンプルなモジュール。
キャッシュは Cache 系APIのモジュールを指定する。実際には Cache::Cache 系でもOK。 Last-Modified、Etag を考慮して処理する。
前回のアクセスから一定時間はキャッシュを返すようにする機能があり、RSS や Atom フィードを取得して利用するのに便利。
URI::Fetch をチョイス。 our を使っているのでそのままでは Perl 5.005_03 では動かないが、use vars に書き換えれば問題なく動く。
CGI プログラムを書いていて、いつも困るのがリグレッションテスト。
パッケージのビルド時に実行するテストスーツ (make check / make test 用テストプログラム群) に含めておきたいが、さすがにその場で Web サーバの下へセットアップするわけにもいかない。 ミニ Web サーバを同梱してテストスーツ内で起動する方法はちょっとおおがかかりだし、ポート番号の選択やらサーバの停止の問題もあって、かなり面倒。
結局、テストスーツの中で環境変数や標準入力など CGI リクエスト環境をセットアップして、CGI プログラムを実行するという王道(?)かつ泥臭いテストを書くことになったりする。
何かいいものはないかと探していたところ、WWW::Mechanize::CGI というものをみつけた。
LWP::UserAgent を継承した WWW::Mechanize モジュールは Web ブラウジングを容易にする有名どころのモジュールである。
WWW::Mechanize::CGI モジュールはさらにこれを拡張したモジュールで、HTTP リクエストを、仮想的に CGI プログラムやサブルーチンへの呼出しにしてくれる。 これを用いるとあたかも Web サーバ上の CGI プログラムにリクエストしレスポンスを受けとっているかのように、テストプログラムを書くことができる。
素晴しい。
さっそく WiKicker のテストを書き換えてみた:
use Test::More tests => 2; use WiKicker::WikICGI::Controller; use WWW::Mechanize::CGI; use File::Temp qw(tempdir); use File::Spec; my $www_dir = tempdir(CLEANUP => 1); my $mech = WWW::Mechanize::CGI->new; $mech->cgi(sub { $ENV{PATH_INFO} = '' if $ENV{PATH_INFO} eq '/'; WiKicker::WikiCGI::Controller->new->run}); $mech->env($mech->env, SCRIPT_FILENAME => File::Spec ->catfile($www_dir . '/wiki'), SCRIPT_NAME => '/wiki'); my $response = $mech->get('http://localhost/wiki'); ok($response->is_success); like($response->content, qr|<title>WikiForum\[WiKicker\]: FrontPage</title>|);
WWW::Mechanize::CGI オブジェクトを new した後、cgi メソッドで CGI サブルーチンを指定するか、cgi_application メソッドで外部 CGI プログラムを指定する。 ここでは直接、CGI サブルーチン (WiKicker::WikiCGI::Controller->new->run を実行)を指定した。
なおここで WWW::Mechanize::CGI が使っている HTTP::Request::AsCGI 0.5 における PATH_INFO の扱いが Apache などとは違って、空でも必ず '/' が入るようになっている。 これだと WiKicker では困るので、サブルーチンのところで修正している。
後は必要ならば WWW::Mechanize::CGI::env で、追加の環境変数設定を行っておく。
セットアップが済めば通常の WWW::Mechanize と同様に get 等でリクエストを行いレスポンスを受けとることができるようになる。
いい。しばらく試してみて不具合がなさそうなら、定番のテストスタイルにしたい。
ちなみに Test::Harness 用の Test::WWW::Mechanize にあわせて、Test::WWW::Mechanize::CGI というものもある。 これらを用いるとさらにテストを書くのが楽になるが、依存するモジュールも多いので無理に使わないほうがいいかもしれない。
などがある。 Apache を使うのがより実際の環境に近いテストができるのだが、通常動いている Apache を使って make test でテストできるようにするとすると「どこに配置するか」などの問題がでてくる。
となればいっその事、自分(一般ユーザ)で専用に Apache を起動した方が良さそうだ。 httpd.conf を用意するのが面倒だが、highperformance.conf 等をみる限り実はそれほど必須の設定は多くないようである。
ということで Debian GNU/Linux sid の Apache (2.0.55-4) で必要な設定は何か試してみた。少なくとも以下の設定は書いておく必要があるようだ。
# httpd.conf for Debian GNU/Linux Apache 2.0.55-4 Listen 9100 ServerRoot . DocumentRoot /home/naney/htdocs ErrorLog error_log TypesConfig /etc/mime.types PidFile apache2.pid
これを httpd.conf として保存して、
/usr/sbin/apache2ctl -f httpd.conf
で起動すればアクセスできるようになる。
/usr/sbin/apache2ctl -f httpd.conf -k stop
で停止。
ServerRoot は起動時の -d オプションでも指定できるのだが、httpd.conf に書いておかないとうまく起動してくれなかった (-X を一緒に指定してデバッグモードにする場合は ServerRoot 無しに -d 指定だけでも動く)。
CGI プログラムを動くようにするとすると例えば次のような感じ。
# httpd.conf for Debian GNU/Linux Apache 2.0.55-4 Listen 9100 ServerRoot . DocumentRoot /home/naney/htdocs ErrorLog error_log TypesConfig /etc/mime.types PidFile apache2.pid LoadModule cgi_module /usr/lib/apache2/modules/mod_cgi.so Options +ExecCGI AddHandler cgi-script .cgi
ディストリビューション独自のパッケージングなどに対応するように、多少泥臭く環境検出する必要があるが、なんとか make test から呼べそうだな。
最近は WWW::Mechanize::CGI がお気に入りなのだが、2つ以上の CGI プログラムにまたがるようなアプリケーションのテストには向かなさそうなので、今度この方法でも試してみたい。
テストファースト開発に慣れてしまうと、テストコード無しにプログラムを書くというのは不安でたまらなく感じてくる。
テストが欲しい。安らぎが欲しい。
今開発している WiKicker ベースの Web アプリケーションもだんだん機能が増えてきて、コードを触るのがコワくなってきた。
今回は Basic 認証等もあるので、WWW::Mechanize::CGI ではなくてきちんと deploy してから Test::WWW::Mechanize でテストすることにした。
Test::WWW::Mechanize、使ってみると WWW::Mechanize + Test::More よりテストを書くのも読むのも楽になった。
deploy が必要なリグレッションテストはさすがに t/ の下に入れておくのはどうかと思う。 プロジェクト的にはビルドサーバを用意して、そこで自動的にテストできるような環境を用意するのが良さそうだ。
PAR といえば Perl スクリプトを実行可能ファイル(Windows なら EXE 形式ファイル)に変換するモジュールとして有名である。
ちなみに実行可能ファイルを作成する部分はは PAR 0.97 より PAR-Packer パッケージに分けられ、PAR 自体はインストールしやすい pure Perl なパッケージになっている。
PAR が提供するもう一つの(こちらが本来はメイン?)機能は、プログラムの実行時に必要な Perl モジュールを PAR ファイルと呼ばれる Perl モジュールアーカイブファイルからロードする機能である。 XS モジュールなどもコンパイルすることができるどこかの環境で1度ビルドして PAR ファイルにしておけば、同じアーキテクチャのホスト上でそのまま利用することができる。
ロードしたい PAR ファイルはファイルパスだけではなく URL でも指定することができ、必要な時にオンデマンドでフェッチさせることができる。 これを使えば Perl プログラムの集中管理可能だ。
PAR 0.951 からは PAR リポジトリというコンセプトが追加され、パッケージ毎に作った PAR ファイルをサーバ上(あるいはローカル)のリポジトリに蓄積してオンデマンドでロードできるようになった。
個別に PAR ファイルを指定する従来の方式に比べてかなり便利そうである。 ということで試用してみた。
まずは
あたりをインストールし準備 OK。
最初に PAR-Repository に含まれている parrepo で。
parrepo create -r /tmp/PAR
PAR リポジトリファイルの中にはデータベースファイルが作成されるが、これは DBM::Deep というアーキテクチャ非依存のものを使っているので、Linux でも Windows でもどちらからでもアクセス可能である (つまり Linux 上でリポジトリをメンテできるということだ)。
次に必要な PAR ファイルを作成する。 作成したいパッケージを展開してビルドし、blib ができている状態で PAR::Dist を使ってパッケージ化する。
perl Makefile.PL make make test perl -MPAR::Dist -e blib_to_par
例えば ActivePerl*1 上で WWW-Mechanize-1.20 を PAR ファイル化すると
WWW-Mechanize-1.20-MSWin32-x86-multi-thread-5.8.8.par
というファイルが作成される。
普段から ActivePerl で必要なライブラリは基本的に自前で PPM パッケージ化して、動作確認した上で PPM リポジトリに蓄積するようにしているので、合わせて次の手順でパッケージを作ることになる。
perl Makefile.PL nmake nmake test perl -MPAR::Dist -e blib_to_par make_ppm
PAR ファイルができたら parrepo でリポジトリに登録する。
parrepo inject -r /tmp/PAR -f xxx.par
例えば先ほどの WWW::Mechanize がリポジトリに登録されている状態で
#!/usr/bin/perl use PAR { repository => 'file:///tmp/PAR/' }; use WWW::Mechanize; my $mech = WWW::Mechanize->new; $mech->get('http://www.example.com'); print $mech->content;
というスクリプトを書いて実行すると、PAR リポジトリから WWW::Mechanize がロードされて正しく実行される。
ここでリポジトリを Web サーバへアップロードして、repository のところに URL を指定するようにすることもできる。 例えばリポジトリを http://www.example.com/PAR/ に配置したとすると
#!/usr/bin/perl use PAR { repository => 'http://www.example.com/PAR/' }; use WWW::Mechanize; my $mech = WWW::Mechanize->new; $mech->get('http://www.example.com'); print $mech->content;
と書き換えることで、インストールしていない WWW::Mechanize を使用できるようになる。
先ほどの Perl スクリプトを get_top_page.pl という名前で保存して pp で実行可能ファイル化する。
pp -o get_top_page.exe -M PAR::Repository::Client get_top_page.pl
とすれば get_top_page.exe という実行可能ファイルが作成される。 WWW::Mechanize はオンデマンドで http://www.example.com/PAR/ からフェッチされるので、アップデートが必要な場合は新しい PAR ファイルを作成してリポジトリを更新するだけでよい。 EXE ファイルを作成しなおして利用者に配付しなすといった作業も不要だ。
さらには実行するスクリプトをも PAR リポジトリに置いておくことが可能だ。
例えば WWW-Mechanize に含まれている mech-dump をオンデマンドにフェッチして実行する実行形式ファイルは以下のコマンドで作成できる。
pp -o mech-dump.exe -M PAR::Repository::Client \ -e "use PAR { repository => 'http://www.example.com/PAR/', \ run => 'mech-dump' }"
ActivePerl では PPM があるとはいえ、普通のユーザにちょっとしたプログラムを使ってもらうのに「ActivePerl をインストールして、PPM パッケージをインストールして、……」というのは手間すぎる。
pp で プログラムに必要なものを全てバンドルした実行形式化ファイルにするという方法ももちろんあるのだが、頻繁にアップデートするようなスクリプトの場合には、起動のための部分だけ pp で作成しておいてあとは PAR リポジトリで集中管理するというのもちょっと魅力的である。
API 経由では Twitter のアイコンを変更できなさそうなので、WWW::Mechanize でアイコン画像をアップロードする Perl スクリプトを書いてみた。
#!/usr/bin/perl use strict; use warnings; use WWW::Mechanize; die "no file name" if @ARGV == 0; my $file_name = $ARGV[0]; my $name = 'ユーザ名'; my $password = 'パスワード'; my $mechanize = WWW::Mechanize->new(); $mechanize->get('http://twitter.com/'); die unless $mechanize->success; $mechanize->submit_form(form_number => 1, fields => { username_or_email => $name, password => $password}); die unless $mechanize->success; $mechanize->follow_link(text => 'Settings'); die unless $mechanize->success; $mechanize->follow_link(text => 'Picture'); die unless $mechanize->success; $mechanize->submit_form( form_number => 1, fields => {'profile_image[uploaded_data]' => $file_name} ); die unless $mechanize->success;
2013年3月29日のPerlCasual #05以来の PerlCasual。 前回と同じ渋谷ヒカリエで今回はディー・エヌ・エーが会場(前回は NHN Japan (現 LINE))。ディー・エヌ・エーが会場のイベントに出るの初めて。横長会場。
メモ:
Naney (なにい) です。株式会社MIXIで SNS 事業の部長をしています。
※本サイトの内容は個人的見解であり所属組織とは関係ありません。