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が手元にないのでパスするとして、LinuxMacOSX(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

 OSXBSDベースのUNIX系OSのため、自前のFSEventsだけでなく、KQueueも使用できます。

実装に際しての戦略(生存。。。じゃないよ)

 おおよそ、以下の2点に絞られると思います。

  1. 定期的に検知システムでチェックを行い、変更があればイベントとしてオブジェクトを発行する
  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" );
300
301 RETVAL = fh;

https://metacpan.org/source/RHOELZ/Mac-FSEvents-0.05/FSEvents.xs

 すいません、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

http://www.sinatrarb.com/images/logo.gif

 で、アプリケーションの動作を確認。
 んで、

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版
速読英単語1 必修編 改訂第4版風早 寛

Z会 2004-11
売り上げランキング : 12771


Amazonで詳しく見る
by G-Tools
 この中で使われていた英文に、うろ覚えだけど、「最近の本ばかり読んで感性を鈍らせるんじゃない。昔からある名作を読んでもっと心を耕せ」的なことが書いてあって、それにもろに影響を受けてる。
 すごく個人的な見解を言うと、自分が5年後とか10年後とか30年後とかにどうありたいか、という人間像を考えてみた時、ビジネス上で自分がどうありたいとか、年収これくらいもらってる、みたいなのって全然浮かんでこなくて、技術的にこれくら身につけていたいとか、含蓄のある言葉を言えてるとか、もっと人を慈しむことができるとか、そういう次元に思考が進む。ビジネス書を読んで感化されて突然発言が変わってるような人って、それってどこまで自分の思考に結びついてるの?それ、ちゃんと自覚的に自分の言葉で話してる?とつい考えてしまう。僕自身に対してすら、ただ乗ってるレールに沿って、自分の知性なんかとは関係なく、その文法で話してるだけなんじゃないかと思っていたりもする。それでは真の人間としての成長ではない。そんなことを、徹夜明けに寝ぼけながらつらつら考えていたりする。

最近のお仕事とか

 先日のエントリの時点ではかなりJSに入れ込んで色々やってましたが、メインでやってる案件に結構影響されていたりします。
 brick.jsについては、弊社のマークアップエンジニアにだいぶ気に入ってもらえたので、そろそろどこかにチュートリアルとか書いてほしいな、と(ぉ。MEがJSを書く時の、処理のまとめ方についてこうしたほうがいいんじゃないか、という願いも込めて作っていたライブラリなので、そういう意図を汲んで使ってもらえるのは本当に嬉しいです。なお、先日リリースした案件にてがっつり使用しております。もうちょっとしたらその案件は弊社サイトでもお知らせします。
 最近はクライアントワーク側のサーバサイドの制作メンバーも増えてきて、以前は孤軍奮闘でやっていたことが、いろんな人のサポートがあったり、メンバーの中には、ある面では僕以上に色々知ってる人がいたりで、かなり良い状況になってきてるな、と思っています。何年か前だったら、自分よりできる人がゴロゴロいると「別に自分はいなくてもいいかも」と思いがちだったのが、今は「もっと面白い状況になりそうだ」と思えるようになったのが、一番の精神的な変化ですね。