Flickr::API2をちょっと読んでみたというお話
flickr.photos.searchを使ってある案件のお手伝いをしたのですが、extrasパラメータで指定した値がレスポンスに入ってない。。。なので、調査のために蓋を開けてみたら驚愕。
# Flickr::API2::Base sub _response_to_photos { my ($self, $photos) = @_; my @photos = map { Flickr::API2::Photo->new( api => $self->api, id => $_->{id}, title => $_->{title}, date_upload => $_->{date_upload}, date_taken => $_->{date_taken}, owner_id => $_->{owner}, owner_name => $_->{owner_name}, url_s => $_->{url_s}, url_m => $_->{url_m}, url_l => $_->{url_l}, url_o => $_->{url_o}, path_alias => $_->{path_alias}, ) } @{ $photos->{photo} }; return @photos; }
えー。。。決め打ち><
仕方ないので、githubからソースを持ってきて、以下のように変更。
diff --git a/lib/Flickr/API2/Base.pm b/lib/Flickr/API2/Base.pm index d7f5673..e07a72f 100644 --- a/lib/Flickr/API2/Base.pm +++ b/lib/Flickr/API2/Base.pm @@ -37,20 +37,13 @@ sub _response_to_photos { my ($self, $photos) = @_; my @photos = map { - Flickr::API2::Photo->new( - api => $self->api, - id => $_->{id}, - title => $_->{title}, - date_upload => $_->{date_upload}, - date_taken => $_->{date_taken}, - owner_id => $_->{owner}, - owner_name => $_->{owner_name}, - url_s => $_->{url_s}, - url_m => $_->{url_m}, - url_l => $_->{url_l}, - url_o => $_->{url_o}, - path_alias => $_->{path_alias}, - ) + my $photo = $_; + $photo->{path_alias} = $_->{pathalias}; + $photo->{date_taken} = $_->{datetaken}; + $photo->{owner_id} = $_->{owner}; + $photo->{owner_name} = $_->{ownername}; + $photo->{api} = $self->api; + Flickr::API2::Photo->new($photo) } @{ $photos->{photo} }; return @photos; diff --git a/lib/Flickr/API2/Photo.pm b/lib/Flickr/API2/Photo.pm index 2218a10..3b4b3ae 100644 --- a/lib/Flickr/API2/Photo.pm +++ b/lib/Flickr/API2/Photo.pm @@ -33,11 +33,19 @@ has 'id' => ( is => 'ro', required => 1, ); -has 'title' => ( is => 'rw' ); -has 'date_upload' => ( is => 'rw' ); -has 'date_taken' => ( is => 'rw' ); -has 'owner_id' => ( is => 'rw' ); -has 'owner_name' => ( is => 'rw' ); + +has [qw/ + title date_upload date_taken owner_id owner_name + description path_alias accuracy datetakengranularity dateupload farm + geo_is_contact geo_is_family geo_is_friend geo_is_public + height_l height_m height_o height_s height_sq height_t height_z + iconfarm iconserver isfamily isfriend ispublic + lastupdate latitude longitude machine_tags media media_status + o_height o_width originalformat originalsecret + place_id server tags views + width_l width_m width_o width_s width_sq width_t width_z woeid + /] => ( is => 'rw' ); + has 'url_sq' => ( is => 'rw', lazy => 1, @@ -68,8 +76,6 @@ has 'url_o' => ( lazy => 1, default => sub { $_[0]->populate_size_urls; $_[0]->url_o; }, ); -has 'description' => ( is => 'rw' ); -has 'path_alias' => ( is => 'rw' ); =head1 METHODS
→ Flickr::API2を修正したので、そのdiff · GitHub
自分用quick hackにつき、特に報告しようとは思ってません。いい修正だとも思ってないので。
AnyEvent::FileWatchをバージョンアップしました
Filesys::Notify::SimpleのAnyEvent版であるところのAnyEvent::FileWatchですが、色々改修しました。大きくは、以下の2点です。
- 監視方法を大幅に変更(F::N::Simpleの実装を大いに参考にさせていただきました)
- Timerクラスの追加
- ちょっとテストも追加
監視方法について、これまでは指定したパス以下すべてのファイルを監視対象にしていましたが、このアップデートからは、各検知システムの「指定したノード以下の変化を検知する」という特性に従うようにしました。モジュール側では指定したノードのみを登録し、イベント単位で配下のファイル等の変化を探索し、イベントオブジェクトにまとめてコールバックに送るようにしました。これにより、各検知システム毎のコールバックで渡されるイベントオブジェクトのインターフェイスを統一することができるようになりました(AnyEvent::FileWatch::Event)。
また、F::N::Simpleにあったt/non_existent_path.tをクリアするため、A::F::Timerモジュールを作成しました。また、環境変数の名前も整備し、PERL_AF_NO_OPT=1が指定できるようにしました。これによって、*nix系以外の環境であったり、ファイル変更の頻度が高いため、定期的に監視するようにしたいといった場合、A::F::Timerを使えば、コンストラクタの第2引数で指定した秒数単位で監視を行います(ディフォルトは1秒)。
→ GitHub - taiyoh/p5-AnyEvent-FileWatch
残りタスク
今把握してるのでは
- もう一つのテストを通す(t/rm_create.t)
- エラーハンドリングを整備
という感じでしょうか。。。
AnyEventで指定したパス以下の変化を検知する
指定したパス以下の変更を検知するのってどうすればいいかな、ということにちょっと興味が湧いたので、調べてみました。
ここでは、Windowsが手元にないのでパスするとして、LinuxとMacOSX(BSD)について触れようと思います。
そもそもOSの検知システムは何があって、Perlからは何でアクセスできるか
以下に対応表を作成しましたので、参照ください。
OS | 検知システム | Perl binding |
---|---|---|
Linux | inotify | Linux::Inotify2 |
BSD系 | kqueue | IO::KQueue |
Mac OS X | kqueue | IO::KQueue |
Mac OS X | FSEvents | Mac::FSEvents |
実装に際しての戦略(生存。。。じゃないよ)
おおよそ、以下の2点に絞られると思います。
- 定期的に検知システムでチェックを行い、変更があればイベントとしてオブジェクトを発行する
- 検知システムの変化を検知し、イベントを発行する(つまりAnyEventの出番)
仮にもここでは、1番目をロングボール戦略、2番目をイベント戦略とします。
ロングボール戦略については、各ライブラリのsynopsisにあるサンプルコードが大体がそうなってるので、特に説明は要らないですね。
とゆーことで、イベント戦略について
AnyEventでイベントを監視するには、ioメソッドを使うのが一般的です、というかそれしかない(AE::Handleは内部的にはAE::ioなので)。つまり、何をAE::ioで監視するかが焦点になります。
さてここで、kqueue、inotify、FSEventsそれぞれのmanを確認してみます。
- kqueue
kqueue システムコールは新規のカーネルイベントキューを生成して記述子を返します。
http://www.lemoda.net/tools/man/ja/2/kqueue
- inotify
inotify_init(2) は inotify インスタンスを作成し、inotify インスタンスを参照する ファイルディスクリプタを返す。
http://linuxjm.sourceforge.jp/html/LDP_man-pages/man7/inotify.7.html
- FSEvents
299 fh = fdopen( self->respipe[0], "r" );
https://metacpan.org/source/RHOELZ/Mac-FSEvents-0.05/FSEvents.xs
300
301 RETVAL = fh;
すいません、FSEventsだけはちょっと場合が違いました。XS側でファイルディスクリプタを開くようにしていますね。まあ、結果オーライで><
いづれにしろ共通点は何かというと、検知システムはファイルディスクリプタを必ず持っているということです。
ここで、AE::ioの引数を振り返ってみると、fhのキー(もしくは第1引数)にはファイルハンドルまたはfileno(ファイルディスクリプタ)を指定せよとあります。
つまり、検知システムのファイルディスクリプタをAE::ioの引数に渡すことで、AnyEventで検知システムのイベントをハンドリングできるようになるということです!
IO::KQueueを使ってその実装サンプルを作ってみました。
#!perl -w #example: KQueueでの実装例。なぜなら一番特殊だったから。。。 use strict; use warnings; use AnyEvent; use IO::KQueue; my $_callback = sub { print "ダァ!!シエリイェス!!"; }; my $kqueue = IO::KQueue->new; open my $fh, '<', '/path/to/watch' or die; $kqueue->EV_SET( fileno($fh) EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_WRITE | NOTE_RENAME | NOTE_REVOKE ); my $w = AE::io $$kqueue, 0, sub { if (my @events = $kqueue->kevent) { $_callback->(@events); } }; AE::cv->recv;
IO::KQueueのファイルディスクリプタはインスタンスをスカラーでデリファレンスすることで取得できますが、Linux::Inotify2ではfilenoメソッドでファイルディスクリプタを、Mac::FSEventsではwatchメソッドでファイルハンドルをそれぞれ取得することが出来ます。
Filesys::Notify::Simpleについて
今年9月に、miyagawaさんがFilesys::Notify::Simpleというモジュールをアップしています。inotify、kqueue、FSEventsだけでなくタイマーでの監視が含まれているので、恐らく(windowsも含め)どの環境でも動くと思います。
自分はAE::timerを使ったアプローチで一度実装できたあとにこのモジュールを見たので、やっぱりもうあったか。。。と気落ちしていましたが、これはロングボール戦略(というかライブラリ備え付けの自前のタイマー)での実装なので、AnyEvent系のモジュールと併用するときに問題が出るかな、と。
それじゃあ自分はもっとAnyEventベースのものを作ってみよう、と思って悪戦苦闘した結果。。。
ついにAnyEventベースのラッパーライブラリができましたっ(キリッ
→GitHub - taiyoh/p5-AnyEvent-FileWatch
AnyEvent::Filesys::Notify::... なんて長すぎるので、ちょっと名前変えました。
このエントリを書くために大急ぎで作ったモジュールです。アルファリリースなんてもんじゃないです。かなりグダグダなので。
動作サンプルはこんな感じです。インターフェイスはFilesys::Notify::Simpleに合わせています。現状は、ひとまず変化があるとなんかコールバックが呼ばれる、くらいのものです。ひどいです。
#!perl -w use strict; use warnings; use AnyEvent::FileWatch; use Data::Dumper; my $filepath = shift or die; # 内部的にInotify/FSEvents/KQueueを切り替えてます my $fw = AnyEvent::FileWatch->new([$filepath]); $fw->wait(sub { my @events = @_; print Dumper(\@events); }); AE::cv->recv;
因みに言うと、監視系モジュールの多くがMooseを使っていて、それほどのもんじゃないっちゅーねん、とちょっとげんなりしています。
まとめ
- はじめてまともにXS読んだ(書いてないけど)
- はじめてkqueueを使ってみた(Cを書いてちょっと動作を見てました)
- AnyEventについてちょっと理解が深まった
- 実は未だに合ってるかどうか不安
- AnyEvent使わないのであれば、Filesys::Notify::Simpleを使う方がいいです
ご指摘についてはお手柔らかにいただけますと幸いです><
nginxを使ったreproxyの方法について軽くメモ
そもそも僕がreproxyについて勘違いしていて、「ヘッダの情報を使ってアクセスってあるけど、そのヘッダはどうやって定義するんや。。。」と思っていたのですが、reproxyのキモはアプリケーションでファイル取得のような重い処理をさせないためのものなんですね。もしかしたら本来の趣旨は違うかもしれないけど、今の僕の理解はそれ。
worker_processes 1; error_log logs/error.log; daemon off; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; server { listen 8081; server_name localhost; location / { proxy_redirect off; proxy_set_header Host $host; proxy_pass http://127.0.0.1:4567; } location /reploxy { internal; resolver 8.8.8.8; set $reproxy $upstream_http_x_reproxy_url; proxy_pass $reproxy; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
例えばこんな感じでnginxを設定する。アプリケーション側は最近熱を上げてるsinatraにて。
require 'sinatra' get '/' do response['X-Accel-Redirect'] = '/reploxy' response['X-Reproxy-URL'] = 'http://www.sinatrarb.com/images/logo.gif' end
これを
ruby -rrubygems test.rb
で起動する。
% curl -i http://localhost:4567/
HTTP/1.1 200 OK
X-Frame-Options: sameorigin
X-Xss-Protection: 1; mode=block
Content-Type: text/html;charset=utf-8
X-Accel-Redirect: /reploxy
X-Reproxy-Url: http://www.sinatrarb.com/images/logo.gif
Content-Length: 40
Server: WEBrick/1.3.1 (Ruby/1.9.2/2011-07-09)
Date: Thu, 20 Oct 2011 13:50:30 GMT
Connection: Keep-Alive
で、アプリケーションの動作を確認。
んで、
sudo /usr/local/sbin/nginx -c /path/to/nginx.conf
でnginxを起動する。この状態でhttp://localhost:8081/にアクセスすると、アプリケーションのX-Reproxy-URLで設定していたsinatraのロゴ画像が出力される。
"Test_mysqld", ported from cpan's Test::mysqld
とまあ、表題の通りですが。
→ GitHub - taiyoh/Test_mysqld-php: from cpan's Test::mysqld
本家のTest::mysqldを見たときに「これは革命や」と感動していたのですが、ちょくちょくあるPHP仕事でも使いたと思っておりまして。しばらくは誰かやってくれないかなぁ、と思っていたのですが、ちょくちょくググッてもなかなか見つからなかったので、作ってしまいました。
使い方は、本家と特に変わりありません。
$my_cnf = array(); // my.cnfに記述する内容を配列でまとめておく $opts = array(); // その他Test_mysqldの設定内容を配列でまとめておく // インスタンス作成 // 本家との一番の違いはコンストラクタの引数で、 // 本家ではmy.cnfに書く内容はmy_cnf => {}にまとめてましたが、 // PHP版ではmy.cnfに記述するためのリストとその他設定のリストを分けてます $mysqld = new Test_mysqld($my_cnf, $opts); // DSN取得 $dsn = $mysqld->dsn(); // DSNを使ってDBに接続 $db = new PDO($dsn); // do something(あとはお好きに)
実装の流れは、ほとんどPerlのものをそのまま使わせていただいております。一部、Perl特有の表記や、PHPだとこう書くと見やすくなるかな、という部分については調整しています。テストについても、TAPで書かれてるものをPHPUnitで書くとこうなるかな、という感じで書いてます。あ、テストについては、最後のマルチプロセスでの試験は省いてしまってます><
symfonyなんかでDB使ったテストを書くとき、よくやる(というかsymfony的にはそれしかサポートしてない)のがenvironment=testで設定しているデータベースにつなぎにいく、というものだと思います。が、それだと万が一オペレーションミスで本番環境で実行してしまったとか、テスト環境だけどDBは共有していて。。。みたいなときに一大事が起こりかねないので、MySQLのインスタンスそのものを別で作ってしまうTest::mysqldの考え方は本当に素晴らしいと思います。
昨日の本の話補足
そもそもどうしてそう考えるようになったかというと、それは大学受験の時に使っていた英語の単語帳にそういう記述があったから。
速読英単語1 必修編 改訂第4版 | |
風早 寛 Z会 2004-11 売り上げランキング : 12771 Amazonで詳しく見る by G-Tools |
すごく個人的な見解を言うと、自分が5年後とか10年後とか30年後とかにどうありたいか、という人間像を考えてみた時、ビジネス上で自分がどうありたいとか、年収これくらいもらってる、みたいなのって全然浮かんでこなくて、技術的にこれくら身につけていたいとか、含蓄のある言葉を言えてるとか、もっと人を慈しむことができるとか、そういう次元に思考が進む。ビジネス書を読んで感化されて突然発言が変わってるような人って、それってどこまで自分の思考に結びついてるの?それ、ちゃんと自覚的に自分の言葉で話してる?とつい考えてしまう。僕自身に対してすら、ただ乗ってるレールに沿って、自分の知性なんかとは関係なく、その文法で話してるだけなんじゃないかと思っていたりもする。それでは真の人間としての成長ではない。そんなことを、徹夜明けに寝ぼけながらつらつら考えていたりする。
最近のお仕事とか
先日のエントリの時点ではかなりJSに入れ込んで色々やってましたが、メインでやってる案件に結構影響されていたりします。
brick.jsについては、弊社のマークアップエンジニアにだいぶ気に入ってもらえたので、そろそろどこかにチュートリアルとか書いてほしいな、と(ぉ。MEがJSを書く時の、処理のまとめ方についてこうしたほうがいいんじゃないか、という願いも込めて作っていたライブラリなので、そういう意図を汲んで使ってもらえるのは本当に嬉しいです。なお、先日リリースした案件にてがっつり使用しております。もうちょっとしたらその案件は弊社サイトでもお知らせします。
最近はクライアントワーク側のサーバサイドの制作メンバーも増えてきて、以前は孤軍奮闘でやっていたことが、いろんな人のサポートがあったり、メンバーの中には、ある面では僕以上に色々知ってる人がいたりで、かなり良い状況になってきてるな、と思っています。何年か前だったら、自分よりできる人がゴロゴロいると「別に自分はいなくてもいいかも」と思いがちだったのが、今は「もっと面白い状況になりそうだ」と思えるようになったのが、一番の精神的な変化ですね。