FormValidator::Lite::Upload を Mojolicious に対応させる

多分こんな感じで行けるんじゃないでしょうか。

FormValidator::Lite::Upload を Mojo::Message::Request に対応して、

--- perl5/lib/perl5/FormValidator/Lite/Upload.pm        2012-06-06 00:40:55.000000000 +0900
+++ lib/FormValidator/Lite/Upload.pm    2012-06-06 00:43:22.000000000 +0900
@@ -29,6 +29,8 @@
             'HTTPEngine';
         } elsif ($q->isa('Plack::Request')) {
             'PlackRequest';
+        } elsif ($q->isa('Mojo::Message::Request')) {
+            'MojoUpload';
         } else {
             if ($q->can('upload')){ # duck typing
                 # this feature is needed by HTML::Shakan or other form validation libraries

FormValidator::Lite::Upload::MojoUpload を作成。

package FormValidator::Lite::Upload::MojoUpload;
use strict;
use warnings;
use base qw/FormValidator::Lite::Upload/;

sub new {
    my ($class, $q, $name) = @_;
    my $upload = $q->upload($name);
    return unless $upload;
        
    bless {
        q        => $q,
        name     => $name,
        upload   => $upload,
    }, $class;
}   

sub size { shift->{upload}->size }
sub type { shift->{upload}->headers->content_type }
sub fh   { shift->{upload}->asset->handle }
    
1;

FormValidator::Lite の新しいバージョンをチェックしたら色々と機能が追加されてて良い感じに便利でした。


[追記 06/15]
TODO: asset が Mojo::Asset::Memory の場合に fh() が多分コケるので、それに対応する。

他のライブラリとの競合を避けたブロックスコープの書き方

色々なライブラリを使用している既存のページなどで、ちょっとした処理を挟みたい場合、Prototype などと、$() が現状・将来的にも競合しないようにするには、

jQuery.noConflict()

を一般的に使いますが、もっと影響範囲を狭く、シンプルに実現する方法が以下のようにあります。

(function($) { // 2. 引数を $ で受けると
   // 3. このスコープでは、$ を jQuery オブジェクトとして参照できる。
    $('#blah').show();
})(jQuery); // 1. 引数に jQuery オブジェクトを渡し

この方法であれば、

j$ = jQuery;

と、定義したときのように、名前空間を汚染することもありません。


無名関数とその引数で、スコープ内の値をコントロールする手法は他にも応用が利きます。

(function(window) {
})(this);

など。

Mojolicious 始めました

最近ようやく Mojolicious に手を出し始めました。プロダクション環境に導入するかは、まだ未定ですが、ソースコードを読みながら試用しています。

Catalyst の原作者が DRY でなく DIY で作っているとのこと。Moose 依存などはなく、コアモジュールにしか依存していません。

ただ、perl-5.10.1 以降を必要とします。(Mojolicious 2.46)

1st リリースされてから、かなり時間が経っている割には、日本語の情報が id:perlcodesample 氏が翻訳されているドキュメント以外は少ないようなので、これからちょっとずつ、ここに得た知識を残して行ければと思います。


↓Mojolicious (2.46) の依存モジュールと、そのモジュールがコア入りした perl リリースバージョン。

B:                      5.005
Carp:                   5
Config:                 5.00307
Cwd:                    5
Data::Dumper:           5.005
Digest::MD5:            5.007003
Digest::SHA:            5.009003
Encode:                 5.007003
Errno:                  5.00504
Exporter:               5
ExtUtils::MakeMaker:    5
Fcntl:                  5
File::Basename:         5
File::Copy:             5.002
File::Find:             5
File::Path:             5.001
File::Spec:             5.00405
File::Temp:             5.006001
FindBin:                5.00307
Getopt::Long:           5
I18N::LangTags:         5.007003
I18N::LangTags::Detect: 5.008005
IO::File:               5.00307
IO::Poll:               5.006
IO::Socket:             5.00307
IO::Socket::INET:       5.006
List::Util:             5.007003
Locale::Maketext:       5.007003
MIME::Base64:           5.007003
MIME::QuotedPrint:      5.007003
Pod::Simple::HTML:      5.009003
Pod::Simple::Search:    5.009003
POSIX:                  5
re:                     5.00405
Scalar::Util:           5.007003
Socket:                 5
Sys::Hostname:          5
Test::Harness:          5
Test::More:             5.006002
Time::HiRes:            5.007003

環境に左右されない PERL5LIB を設定する .profile の書き方

プロジェクト用のモジュールを extlib とか専用のディレクトリを掘っていると、それを @INC に突っ込まなくてはいけないので、ブートストラップスクリプトで調整したり、開発用に .bashrc や .profile に書くと思いますが、モジュールがインストールされるディレクトリ名は、

./extlib/lib/perl5/i486-linux-gnu-thread-multi (32bit Ubuntu)
./extlib/lib/perl5/darwin-thread-multi-2level (OSX)

のように、アーキテクチャ名が入ったりするので、環境によってディレクトリ名がまちまちで書き換えないといけません。

なので、下記のように .profile を書くと環境に左右されないので便利です。

ARCHNAME=$(perl -MConfig -e 'print $Config{archname}')
EXTLIB=./extlib/lib/perl5:./extlib/lib/perl5/$ARCHNAME
PERL5LIB=./lib:$EXTLIB

export PERL5LIB=$PERL5LIB

[追記]
lestrrat 氏がコメントで PERL5OPT を利用する方法を紹介して下さいました。ありがとうございます!

export PERL5OPT=-Mlib=extlib/lib/perl5

lib.pm も Config.pm を使って archname 以外も見て上記の方法よりもよしなにしてくれます。より、.profile がスッキリしますね。

AWS Route53 でホスト名にワイルドカードを設定する方法

下記のように に *.example.com を指定して に IP アドレスを指定するだけです。

に CNAME を指定することもできるようです。(未確認)

<?xml version="1.0" encoding="UTF-8"?>
<ChangeResourceRecordSetsRequest xmlns="https://route53.amazonaws.com/doc/2011-05-05/">
    <ChangeBatch>
        <Comment>This change batch creates a A record for *.example.com.</Comment>
        <Changes>
            <Change>
                <Action>CREATE</Action>
                <ResourceRecordSet>
                    <Name>*.example.com.</Name>
                    <Type>A</Type>
                    <TTL>3600</TTL>
                    <ResourceRecords>
                        <ResourceRecord>
                            <Value>192.168.0.1</Value>
                        </ResourceRecord>
                    </ResourceRecords>
                </ResourceRecordSet>
            </Change>
        </Changes>
    </ChangeBatch>
</ChangeResourceRecordSetsRequest>

Plack ベースで作った Web サービスとそのシステムアーキテクチャ

本日、TVTalk という Web サービスをリリースしました。

Twitter 上で、テレビ局のハッシュタグが付いているツイートを拾って、放送中の番組情報と紐付けるという、アグリゲーターサービスです。リアルタイムにタイムラインを追うにも、放送済み番組の内容をチェックするのにも使えますので、みなさんぜひ使ってみてください。ブックマークやいいね!も良かったらお願いします!

今日は、この Web サービスの裏側のシステム構成を紹介したいと思います。

構成

Web サーバー

Web はフロントに nginx をおいて、静的ファイルは nginx にサーブさせ、拡張子がついていない URL のみバックエンドの Starman に渡しています。nginx の設定ファイルは以下のようになっています。

    location / {
        root /home/tt/repos/tvtalk/static;
    }

    location = / {
        access_log  /var/log/tvtalk/nginx_service/access.log;
        proxy_pass http://localhost:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location ~ /stream/ {
        access_log  /var/log/tvtalk/nginx_stream/access.log;
        proxy_pass http://localhost:8082;
    }

    location ~ /[^/.]*(\?|$) {
        access_log  /var/log/tvtalk/nginx_service/access.log;
        proxy_pass http://localhost:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
    }

上記設定から分かるように、ページを生成するのは Starman プロセス、ツイートのストリームを吐き出すのは Twiggy と分けています。

放送中のタイムラインは、jquery.ev.js から long-poll で Twiggy + Tatsumaki に繋いで実現しています。

Streaming API との自動再接続処理

Twitter の Streaming API は、何時間か毎に接続が切れるので、自動再接続する処理を施しています。ツイートをアーカイブするクロウラープロセスは、普通に while ループで回し、ストリームを吐き出すプロセスは、AE::timer で接続状態をチェックしつつ、自動再接続処理をし、Twiggy::Server->register_service でハンドラ登録して、AE::cv->recv でブロックしています。

Streaming API の接続制限

Streming API を利用するにあたって注意する点は、Streaming API に複数のコネクションを張ると切られまくるので、複数のトラックワードを指定したい場合、複数のコネクションを張るのではなく、単一のコネクションを張って、on_tweet 時に、どのワードをトラックしたかアプリケーション側で判別する必要があるということです。この辺の注意事項は公式の API ドキュメントに書いてあります。

コネクションを張る前に、Tatsumaki::MessageQueue->instance でトラックワード数ぶんのキューを用意してあります。

現時点では、タイムラインは jquery.ev + long-poll で実装していますが、そのうち、DUI.js + multipart/mixed に置き換えたいと思っています。

自作フレームワークについて

Airy というオレオレフレームワークを作って使っています。
Moose などには依存しないように、なるべく汎用的にせずに、機能を限定してなるべく薄くなるように作っています。まだ色々と実装途中ですが、機能・実装でアドバイスあればぜひお願いします。

my と state のパフォーマンス比較

最近の Linux ディストリビューションperl は 5.10 以上になってきた感じなので、最近は state など新しい機能を使うようになってきました。

そこでちょっと気になったので state のパフォーマンスを計測してみました。ベンチマークコードは以下。動作が全く同じ訳ではないですが。

use 5.010;
use strict;
use warnings;
use Benchmark ':all';

cmpthese(timethese(-1, {
    'my'    => \&_my,
    'state' => \&_state,
}));

{
    my $v = 1;
    sub _my { $v }
}

sub _state {
    state $v = 1;
}

結果は以下のように、ベンチマークを実行する毎に違う結果が出ました。実行環境は Ubuntu 10.04 の perl 5.10.1 です。

Benchmark: running my, state for at least 1 CPU seconds...
        my:  1 wallclock secs ( 1.08 usr +  0.00 sys =  1.08 CPU) @ 8972488.89/s (n=9690288)
     state:  4 wallclock secs ( 2.14 usr +  0.00 sys =  2.14 CPU) @ 10718488.79/s (n=22937566)
            Rate    my state
my     8972489/s    --  -16%
state 10718489/s   19%    --

Benchmark: running my, state for at least 1 CPU seconds...
        my: -2 wallclock secs ( 1.15 usr +  0.00 sys =  1.15 CPU) @ 9459979.13/s (n=10878976)
     state:  2 wallclock secs ( 1.02 usr +  0.00 sys =  1.02 CPU) @ 7710116.67/s (n=7864319)
           Rate state    my
state 7710117/s    --  -18%
my    9459979/s   23%    --

Benchmark: running my, state for at least 1 CPU seconds...
        my:  2 wallclock secs ( 1.22 usr +  0.00 sys =  1.22 CPU) @ 9024628.69/s (n=11010047)
     state:  2 wallclock secs ( 1.04 usr +  0.00 sys =  1.04 CPU) @ 9830399.04/s (n=10223615)
           Rate    my state
my    9024629/s    --   -8%
state 9830399/s    9%    --