As Sloth As Possible

可能な限りナマケモノでありたい

PlackでSinatraチックな何かを作ってみた

こないだ言ってたばりったーは実はもう完成してたんだけど、なんかサンプルアプリのつもりが作ってるうちに段々ガチなフレームワークと化してきて、大したアプリじゃないのに中身は意外と複雑でしかもバグだらけ、みたいなことになってしまった。

うーんこれ公開してもいいけど参考になんねーよな、つか飽きたなーとか思って別の遊びを始めたら意外とそっちが楽しくなってきちゃって、こんなものが出来た。

faultier's Asagao at master - GitHub

何これ

Plackを使ってSinatraっぽい感じでWebアプリを書ける雰囲気のフレームワークみたいな(弱々しく)。一応README.jaを読むとどんなものかは何となく伝わるんじゃないかしら。ドキュメントとサンプルアプリくらいは作ってみようとは思いますが、何ができるか試してみる際にはソース読んでくだしあ。(追記)簡単なサンプルコードドキュメント(予定地)作ったよ。

TODO

  • Sinatraが出来ることは一通り出来るようにはしたい
  • 例えばPlack::Builderのenableとかを直に書けるようにするとか
  • passとかbeforeとかafterとか
  • configとかdispatchとか適当すぎるのでどうにかする
  • 今テンプレートがText::MicroTemplateだけなので他のも対応する

思ったこと

Sinatraの中身は意外とサッパリしてた。割とさらっと読めるレベル。

あとこういうDSL的な見た目になるものを実装するのはやっぱりRubyのが楽でいいなぁと思った。同じことやるのにPerlの方がもうちょっとトリックがいる感じ。いやまぁ、慣れの問題もあるんだけども。

なんだか最近個人的にPlack期到来中なんだけど、面白いからみんなもっといじるといいよ。

追記

ブコメで「CPANにDancerってのがありますよ」と教えてもらった。うはぁ。ほんとだ、被ってら。向こうはバージョン1.0ですし当然ながらもっとずっとちゃんとしてます。ひー。わかっててやったんならともかく知らなかったのはとても恥ずかしい。

強いて言うなら、向こうは全部自前実装だけどこっちはPlackに乗ってるっていうのが強みなくらいかなぁ。動かせる環境が増えても楽とかミドルウェアを重ねられるとか、ほら、いいことあるよ。あと俺TemplateToolkit嫌いなのでText::MicroTemplateでviewを書けるようにしたとか。…でもDancerは最小限の依存しかないのでインストールやデプロイは楽そうだけど、AsagaoはAny::MooseとかPath::Dispatcherとか色んなものに依存しててレンタルサーバとかに上げにくいかもしれない。

むしろお手本にさせてもらおう。後でソース読もう。

さらに追記

ブコメで「Dancer::Handler::PSGI があるから plack の上でもうごかせるよ!」って教えてもらいました。

あれー。

あれー。

あれー。

残念でござる。もう完全に意味ないでござる。まぁ勉強だと思ってとりあえず作ろうと思ってたところまでは全部作っちゃうか。

faultierfaulist  at 15:06  | コメント( 0 )  | トラックバック( 0 )  | この記事をクリップ! このエントリをつぶやく

生のつぶやきとブログの記事

twitterの呟きをそのままブログに載せるのってどうなの?っていう記事を見て、最近思ってたことで書いてなかったなぁってことがあったのを思い出した。

元記事に書いてあることには概ね同意で、俺も個人的には「ただ載せる」だけならやんなくてもいいのになぁと思ってるけど、元記事には結構「イヤなら見るな」「別に書き手は”あなたのために”書いてるわけじゃない、あなたが見たい/見たくないものを考慮して書く必要なんかない」ってコメントが付いてたりして、それに対してもまぁそれもそうだよなぁとも思ってる。俺がどうしてるかというと、readerにその日一日のポストをまとめた記事が上がってきても単にjjjって押してスルーするだけだし、あまりそれの頻度が高いようならレートを落として週一で見るだけにしたりもういっかーってんで購読停止したりすることもあるけど、だからってそのブログを書いてる人に「もっと読む人のことを考えた投稿をすべきじゃないですか」なんて意見したりはしない。受け手側が好きに取捨選択すればいいだけの話だとしか思わないしそうするのがWebだろうとも思ってる。

けどまぁ、ねぇ、と思うこともあるのですよ。なんと言うか、もちろん腹立たしいとかではなくて、勿体ないなぁ、というか。

twitter自体は本当にただ「つぶやく」ことができるというだけのサービスなので、そこに何を流そうが流す方の自由だし、そこから何を受けとろうが受けとる方の自由。生データ。どのくらい「生」なのかと言うと、例えば鉱山にある岩塊だったり渓谷に流れる水だったり森に生えてる野草だったりするくらいの「生」加減だと思うんだよね、あれって。

で、そういうものって、もともと「そこにある」分には「そのまま」でいいと思うし、「欲しい人は自分で山に入って取ってきてね」ってスタンスだったらそれはそれでいいんだけど、「街に持ってきてみんなに配ったり売ったりしよう」と思ったら、普通は岩塊や水や野草のままじゃなくて、岩塊は鉱物を抽出して金属塊にしたり、水はペットボトルに入れておいしい水ってラベル貼ったり、野草は薬や料理にしたりするよね、っていう。そして個人的には「twitterもやっていてブログも持っている」人が「twitterで見られるものを敢えてブログに持ってくる」というのはきっと「山奥の資源を街に持って帰ってくる」に近いような位置付けじゃないのかなぁ、と思う。

別にだからどうだと言うこともない。ブログにしろtwitterにしろ、あくまで道具なので使う人が使いたいように使えばいいだろうと思うし、だから一日分のつぶやきをまとめて貼ろうが、そもそもブログそのものをtwitter的な「生のデータを生えたい放題にさせとく場所」にしたって全然構わないと思う。俺自身、(このメインブログでは基本的にそれはやってないけど)サブブログでは本当につぶやきとか日記レベルのことを書いてたりする。上に書いたようなことも単に「俺が個人的に」「受け手として」思うだけの話で誰に何を提案するわけでもましてや強制するつもりもない。けど、「勿体ないからどうせなら加工してから店に置いときゃいいのになぁ、そうしたらきっとみんなもっと喜ぶだろうに」というなんだろ、感想、みたいなものはよく抱く。素材だけじゃなくて「編集する」っていう「料理人の腕」も見てみたいなぁ、とか。そんなもやっとした結論。

faultierfaulist  at 16:20  | コメント( 0 )  | トラックバック( 0 )  | この記事をクリップ! このエントリをつぶやく

Text::MicroTemplateで遊ぶ

こないだ言ってたPlackアプリのサンプルでは、出来るだけ普段使ってない構成にしようと目論んでて、そのために例えばテンプレートエンジンにはText::MicroTemplateを使ってみたりしている。

Text::MicroTemplateはシンプルで軽くてなかなか良かった。ちなみにこんな感じで使う。

use Text::MicroTemplate qw(:all);

my $html = render_mt('Hello, <?= $_[0] ?>', 'faultier')->as_string;

でもファイルから読みたいよねーそれ実装しなきゃなのかなーとか思ってたらちゃんとText::MicroTemplate::Fileってのがあって、こっちを使えばキャッシュも使ってくれるしラッパーの機能とかもあるしで素敵だ。

?# hello.mt
Hello, <?= $_[0] ?>
use Text::MicroTemplate::File;

my $mtf = Text::MicroTemplate::File->new(use_cache => 1);
my $html = $mtf->render_file('hello.mt', 'faultier');

で、ここまでちょこちょこ弄ってみて思ったけど、やっぱりviewの中で@_とかを触るのはなんか嫌だ。そりゃ、引数にHashRef渡してやるとかテンプレートの頭で変数に入れてやるとかすればいいんだろうけど、そういう生々しい情報はviewで扱いたくない。つまり、やりたいのはこういうこと。

?# hello.mt
Hello, <?= $name ?>
use Text::MicroTemplate::File;

my $mtf = Text::MicroTemplate::File->new(use_cache => 1);
my $html = $mtf->render_file('hello.mt', { name => 'faultier' });

で、どの辺をいじればいいんだろうと読んでたら、Text::MicroTemplateのbuildメソッドの中身はこんな風になっていた。

    my $expr = << "...";
package $_mt->{package_name};
sub {
    ${_mt_setter}local \$SIG{__WARN__} = sub { print STDERR \$_mt->_error(shift, 4, \$_from) };
    Text::MicroTemplate::encoded_string((
        $_code
    )->(\@_));
}
...

ふむふむ。この$_mt_setterってなんだろうなと見てったら、デフォルトでは空、つまり何もしないんだけど、ここに適当なコードを突っ込むとテンプレートを処理する前にやることを追加できるようだ。

use Text::MicroTemplate qw(:all);

$Text::MicroTemplate::_mt_setter = 'my $name = shift;';
my $html = render_mt('Hello, <?= $name ?>', 'faultier')->as_string;

おお、出来た。んでも実はText::MicroTemplate::Fileの方ではこれはできない。build_fileの中でbuildを呼ぶ前に

local $Text::MicroTemplate::_mt_setter = 'my $_mt = shift:';

ってやっちゃってるし、テンプレートに渡す引数がわかるのはrender_fileの段階だし、大体これ多分外から挙動を制御する用じゃないよなぁ。というわけで当初は自前でモジュール組んでたんだけど、良く考えたら事前に引数を名前付きで渡せないこと以外は概ねText::MicroTemplate::Fileで良いわけで、作ってるうちにどんどん似てきてしまったのでじゃあText::MicroTemplate::Fileを拡張すればいいじゃんってことになった。それでこんなものを作ってみた。

faultier's p5-text-microtemplate-file-bindvars at master - GitHub

use Text::MicroTemplate::File::BindVars;

my $mtf = Text::MicroTemplate::File::BindVars->new(use_cache => 1);
my $html = $mtf->render_file('hello.mt', { name => 'faultier' });

動いたー。一応Text::MicroTemlate::Fileと同等の内容のテスト通るし、ベンチも取ってみたらキャッシュ無しのときでほんのちょびっと速いくらい、キャッシュ有りだとちょっと残念なことに15%ほどもパフォーマンス落ちちゃうけどそれでもTTの4、5倍くらい速いしまぁいっかーなんて思ったりしました。どうせここ全然ボトルネックじゃないし。

とそんな感じで一通り書き終えてこの記事をまとめてるところでこんなものがあるのに気付いた。

Text::MicroTemplate::Extended - search.cpan.org

…これでいいじゃん。マクロとかブロックとかあるし高機能じゃん。最初に良く調べなさいよって話ですねほんと。CPANで関係ありそうなモジュール探すとか基本中の基本じゃん。残念すぎる。

せっかくなので取ったベンチ結果。

Benchmark: timing 10000 iterations of T::MT::E, T::MT::F, T::MT::F::BV, TT...
  T::MT::E: 16 wallclock secs (16.01 usr +  0.61 sys = 16.62 CPU) @ 601.68/s (n=10000)
  T::MT::F: 17 wallclock secs (16.00 usr +  0.39 sys = 16.39 CPU) @ 610.13/s (n=10000)
T::MT::F::BV: 16 wallclock secs (15.85 usr +  0.39 sys = 16.24 CPU) @ 615.76/s (n=10000)
        TT: 46 wallclock secs (45.60 usr +  0.55 sys = 46.15 CPU) @ 216.68/s (n=10000)
              Rate           TT     T::MT::E     T::MT::F T::MT::F::BV
TT           217/s           --         -64%         -64%         -65%
T::MT::E     602/s         178%           --          -1%          -2%
T::MT::F     610/s         182%           1%           --          -1%
T::MT::F::BV 616/s         184%           2%           1%           --
Benchmark: timing 10000 iterations of T::MT::E, T::MT::F, T::MT::F::BV, TT...
  T::MT::E:  1 wallclock secs ( 1.04 usr +  0.06 sys =  1.10 CPU) @ 9090.91/s (n=10000)
  T::MT::F:  1 wallclock secs ( 0.75 usr +  0.05 sys =  0.80 CPU) @ 12500.00/s (n=10000)
T::MT::F::BV:  1 wallclock secs ( 1.10 usr +  0.06 sys =  1.16 CPU) @ 8620.69/s (n=10000)
        TT:  5 wallclock secs ( 4.92 usr +  0.00 sys =  4.92 CPU) @ 2032.52/s (n=10000)
                Rate           TT T::MT::F::BV     T::MT::E     T::MT::F
TT            2033/s           --         -76%         -78%         -84%
T::MT::F::BV  8621/s         324%           --          -5%         -31%
T::MT::E      9091/s         347%           5%           --         -27%
T::MT::F     12500/s         515%          45%          37%           --

できること少ないのに負けてるじゃん…。ちなみに、T::MT::EとT::MT::F::BVは全く同じテンプレートがレンダリングできたので同じの使ってる。キャッシュの仕方が悪いんだろうなー。でもT::MT::Eの方はオブジェクト生成時に引数指定しなきゃだから、複数ファイルをレンダリングさせたらちょっと変わったりするかしら。あとはまぁ高度なことをやらせればその分速度落ちるけど、そもそもT::MT:FとT::MT::F::BVには出来ないことなのでそこで差がついたとしてもT::MT::Eを選ぶなぁ。しかしそれにしてもTT遅いな。残念な子ですわね。

追記

やってみたけど大してかわんない。そもそも何万何十万て回数やるうちの数回オブジェクト生成が増えたところで差が付くわけもないだろっつー。あまりに意味ないのでもうちょい見直そう。

さらに追記

ちょっと試行錯誤してみたけど上手くいかなかった。思い付きで作ったものじゃ勝てないかー。ほとんど何もやってないんだけどな…。で、単純に1ファイルだけのテンプレートでキャッシュ有りだと当然のことながらText::MicroTemplate::Fileのがずっと速い(余計なことしてないから)。十万回やったら3秒くらい差がついた。T::MT::EとT::MT::F::BVの差はまぁ数%ってところ。十万回やって0.3秒違うだけだしTTとの40秒の差とくらべたらなんてことないかな。

ラッパー有りだと細かい処理の差よりそっちのが重いのでT::MT::FとT::MT::F::BVの差が3割弱から1割強まで縮まる。悪くはない。是非T::MT::Eのテンプレート継承機能との差を試したかったんだけどなんか上手く動かせなくて悩み中。TTは十万回やると1分くらい帰ってこなくなるのでもうなんかいいやって感じになってきた。いや、うちのサービスのほとんどがTT使ってるんだからキャッシュの仕組みとか工夫してさえいれば大した問題じゃない(だってDBとかのがずっと遅いんだもの)のはわかってるんだけど、こんだけ速度に差が付いてて、素のPerlで書けて、HTMLのエスケープもデフォルトでやってくれて、とか考えたらこっち使いたくなるねぇ。

faultierfaulist  at 22:26  | コメント( 2 )  | トラックバック( 0 )  | この記事をクリップ! このエントリをつぶやく

「Plackで一発ネタは俺に任せろ!」バリバリ「やめて!」

タイトルからして既に残念な感じが漂ってるけど、前回の記事で言った通りPlackでアプリを作ってみようと試行錯誤中。ちなみにこないだサーバをApacheからnginxに変えたついでにHTTP::EngineのアプリもFastCGIで動かすようにしたので、PlackのアプリもFastCGIで動かしてみた。設定はこんな感じ。

# app.psgi
use NetaKit::TweetProxy::App;
use Plack::Builder;
builder {
    #mount '/baritter' => builder {
        NetaKit::TweetProxy::App->new->to_app;
    #};
};
#!/usr/bin/env perl
# bin/fcgi.pl
use strict;
use warnings;
use File::Spec;
use FindBin qw($Bin);
use Plack::Server::FCGI;
use lib File::Spec->catfile( $Bin, qw(.. lib) );
my $app = eval { require File::Spec->catfile( $Bin, qw(.. app.psgi) ) };
my $server = Plack::Server::FCGI->new(
    nproc       => 4,
    listen      => '/tmp/plack_netakit.sock',
    pidfile     => '/tmp/plack_netakit.pid',
    detach      => 1,
);
$server->run($app);
# nginx.conf
server {
    listen       80;
    server_name  localhost;
    location / {
        root   /var/www/faultier.jp/htdocs;
        index  index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   share/nginx/html;
    }
    location /baritter {
        set $script "";
        set $path_info $uri;
        fastcgi_pass unix:/tmp/plack_netakit.sock;
        include fastcgi_params;
    }
}

PlackのがH::EよりもRackと似たような感じに書けて楽だった。DLSとかURLMapとかもあるしね。あと、Rackで言うところのconfig.ruにあたるのがapp.psgiなんだけど、せっかくこれ書いたのにfcgi.plで同じのまた書くのやだなぁと思ってなんとなくrequireしてみたら普通に行けた。素敵。使い方これでいいのか疑問だけど。mount使おうとして上手くいかなくてコメントアウトした形跡があるのはまぁ御愛嬌。

baritter

いい加減彼女が404ばかりで飽きてきたので今回は真面目になんかしら遊べるものを作ってみることにした。

バリバリ 嘘じゃないもん

まぁアプリ名とかから推測つきそうなもんですが、ようはtwitterのポストを拾ってきてアレげなフォーマットにはめ込んで表示するだけの簡単なお仕事をするサービスです。サンプルなので単語を抽出するとかそういう凝ったことは決してやりません!(キリッ。上のスクショはそもそも元発言(もしくは発言者の脳味噌)が残念なのであんまり変わらないけど、津田さんとか池田信夫さんとかにこのフィルタをかますと脱力感が半端ないので3秒くらいは楽しめた。

ちなみに成果物はこんなだけど、内部ではPlackとText::MicroTemplateとData::ModelとCoroを使ってるとかいう、構成だけ見ると面白いサンプルになってるんだぜ。無駄遣いにも程があるぜ。出来たらソースも公開する予定。

faultierfaulist  at 04:03  | コメント( 5 )  | トラックバック( 0 )  | この記事をクリップ! このエントリをつぶやく

nginxとUnicornでRackアプリを動かす

どうやらUnicornというのが良いらしいという噂を聞きつけたので、どんなもんじゃろと試してみることにした。

Route 477 - 大規模Railsサイトのための新しいHTTPサーバ、Unicorn

Unicornてのは何者なのかと言うと、Rack及びRailsに対応したRubyのWebアプリ用のHTTPサーバ。詳しくは上の記事を読んで下さい。githubでも使ってるそうだ。あと、名前が格好良い(あんまり関係ない)。

まずはunicornの設定

と言っても、gem install unicornしてconfig.ruがあるディレクトリでunicornコマンドを叩けば、thinとかと同じようにサーバが起動する。rackup互換のオプションも付いてるので特に悩むこともないと思う。あとは普通にApacheとかでプロキシの設定してやるなりなんなりすればすぐ使える。

それだけだと大して面白くないので、折角だから上の記事に書いてあるように、nginxでソケットでっていう設定をしてみる。先にUnicornの方の設定。ちなみにサンプルに使ったのは例によって彼女が404のやつ。今現在はApache2+Passengerで動いている

# unicron.conf
worker_processes  4
working_directory '/var/www/rackapp'
listen '/tmp/rackapp.sock', :backlog => 1
listen 4423, :tcp_nopush => true
timeout 10
pid '/tmp/rackapp.pid'
preload_app  true
stderr_path '/var/log/rackapp.log'

before_fork do |server, worker|
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end

  sleep 1
end

after_fork do |server, worker|
  addr = "127.0.0.1:#{4423 + worker.nr}"
  server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => 1)
end

中身はほとんどUnicornの設定例からコピペ。ちなみに、nginxを推奨してるだけあってnginxの設定に似せたDSLになってるけど、実態は見ての通りRuby。その気になれば動的に設定をごにょごにょみたいなこともできる。ログはThinみたいによしなにやってはくれないけど、その辺は設定でLoggerを渡すなりRackのミドルウェアを使ってなんとかするなりすればOK。

んでこれを適当なファイル名で保存したら、次のようにunicornコマンドを叩く。

$ unicorn -D -c /var/www/rackapp/unicorn.conf /var/www/rackapp/config.ru

-Dオプションでデーモン起動、-cオプションは設定ファイルの指定、最後の引数はrackupファイルのフルパス。ちなみにこれを/var/www/rackappじゃないところで実行してみたんだけど、最初は「working_directoryとか指定してるから、そこにあるconfig.ruを勝手に読んでくれたりしないかな」とか思ってたんだけどやっぱりダメで、その後相対パスで指定してみたらrequireでコケて「そんなファイルねーよ、寝言は寝て言え」と怒り出したので、絶対パスにしたら行けた。

この時点でhttp://localhost:4423/がちゃんと見られることと、unicorn.sockが出来てることを確認。よし次はnginx。

nginxのプロキシ設定

こんな感じ。

# nginx.unicorn.conf
upstream rackapp {
    server unix:/tmp/rackapp.sock;
}

server {
    listen       80;
    server_name  localhost;

    location / {
        proxy_pass  http://rackapp;
    }
}

これを大本のnginx.confのhttpのブロックのどこかでincludeしてやる。一応言っとくとこれ以外にも普通のnginxの細かい設定はしてるけど、上の例では省略してます。

ちなみに、ソケットでやるように設定してるからこうなってるけど、TCPで良ければproxy_passのところをhttp://localhost:4423とかにしてやっても普通に動く。

とりあえずこれで完成。あとはnginxを起動してやれば、ちゃんと見られる。素敵。

インターフェースが統一されてるって素敵よね

いやぁ、やっぱRackでインターフェースが統一されてるって、良いですよね。アプリ側あんまり弄らなくてもさくっと移行できるわけですし(っていう話をこないだ書いた)。あと、ついでに同じサーバで動いてたHTTP::Engineのアプリも、apache+modperlからnginx+fcgiに移行してみたけど、こっちもちょこっと設定変えたくらいでアプリ側は全然弄ってない。良いなー。こうして新しい環境が出てきたりしたときにはやっぱりRackやなんやらが真価を発揮する。折角なので次はPlackでH::Eのときと同じことをやってみようと検討中。

んで、結局Unicornは良いの?イイの?

さぁ…ベンチ取ってないのでなんとも。取っても「彼女が404」じゃあんまり参考にならないんだよなー。ただ、アプリをロードし終わってからforkするとかnginx側に気付かせない用に再起動できるとかその辺だけでも十分魅力的ではある。あと、再起動は早かった。流石。

faultierfaulist  at 02:10  | コメント( 0 )  | トラックバック( 1 )  | この記事をクリップ! このエントリをつぶやく
livedoor プロフィール
記事検索
月別
カテゴリ別
あわせて読みたい
あわせて読みたい
なかのひと
  • livedoor Readerに登録
  • RSS
  • livedoor Blog(ブログ)