Plack::Requestとか使って小さなWebアプリを作ろう!
ヒゲをはやして、髪をモヒカン気味に切って、ベストを着ていたら「dankogaiさんですか!」と呼ばれまくって失礼ですがショックを受けてしまい、とりあえずヒゲを剃ってみたyusukebeです。気合い入れて書きます。
はじめに
さて、今回はPlack::RequestやTemplate-Toolkit、XML::Feedといったモジュールを使って、小さな、だけどもなかなか使えるWebアプリを作る過程を紹介します。これを通して、今話題であるPlackについてやWebアプリの仕組みについて多少でもわかっていただければこれ幸いでございます。
実はこのネタ昨日の夜考えて作ったものであります。というのも「YouTubeの動画を垂れ流しで好きなように、みてーな」とふと思ったからです。例えばYouTube内で「Perfume」と検索をすると個別の動画以外に「再生リスト」または英語では「Playlist」というユーザーが集めた動画集が出てきます。こいつを好きな画面サイズにて連続で見ていければよさげだと思いました。YouTubeのアプリを作っているわりにはYouTube本体の機能をあまりしらなかったりして、同じ様なことができてしまうかもしれませんが、そこは目をつむっていただき、いい題材なのでこのニーズを叶える小さなWebアプリをPlackベースで作ってみることにしました。あとRemedieでもいいじゃんっていうツッコミも放置プレーします。
まとめると、YouTubeの再生リストを指定すると、動画の一覧がでてきて、それを連続で見ていくことができるビューアーのWebアプリをPlackベースで作ってみましょうというのが趣旨になります。
そして、名前をノリで「Chekera!」と名付けました。先に、紹介しておきますが、githubにレポジトリも設けていまして、場所はこちらになります。
YouTube API を使う
再生リストにどのような動画が含まれているかを取得するためにYouTube APIを使います。YouTube APIは基本的に情報取得系のメソッドですと、クエリを含んだリクエストを投げればAtomフィードに独自のフォーマットを加えたYouTube GData形式でレスポンスが返ってきます。再生リストのAPIについては以下のリファレンスページで知ることができます。
http://code.google.com/intl/ja-JP/apis/youtube/2.0/reference.html#Playlist_feed
このページからすると、以下のようなURLへGETリクエストを発行すればビデオフィードが得られるとのことです。
http://gdata.youtube.com/feeds/api/playlists/再生リストのID
では、早速どのように情報が得られるかをXML::Feedというモジュールを使って取得&パースしてみましょう。
use XML::Feed; my $playlist_id = 'CD06A83DE0A8F4B0'; my $feed = XML::Feed->parse( URI->new("http://gdata.youtube.com/feeds/api/playlists/$playlist_id") ); for my $entry ( $feed->entries() ) { print $entry->title . "\n"; print $entry->link . "\n"; }
このスクリプトを実行すると
http://www.youtube.com/view_play_list?p=CD06A83DE0A8F4B0
こちらのPerfume関連の動画リストに含まれるビデオのタイトルとリンクが出力されます。YouTubeの動画というのはビデオのIDさえわかれば、そのサムネイル画像やプレイヤーのURLを簡単に構築できるので、以下の様な正規表現をループの中に加えてビデオIDとタイトルだけ後ほど利用するようにしましょう。
my ($video_id) = $entry->link =~ /\?v=([^&]+)/;
Plackアプリ(.psgi)のひな形
Plackの詳細については、現在進行中のmiyagawaさんによる「Plack Advent Calendar」に導入から丁寧に解説されているので、そちらを参考にしてください。
今回は、今回扱う最低限のPlack周りについて解説します。Chekera!では、Plack::RequestというPlackとは別になっているモジュールが、後ほどの拡張を見込んで便利そうなので使用します。また、jsやcssファイルを静的コンテンツとして配信したいためにPlack::Middleware::StaticをPlack::Builder経由で呼び出しています。.psgiファイルのひな形は以下になります。
use Plack::Builder; use Plack::Request; my $app = sub { my $env = shift; my $req = Plack::Request->new($env); return root($req) if $req->path eq '/'; [ 404, [ "Content-Type" => "text/plain" ], ["Not Found"] ]; }; sub root { my $req = shift; my $res = $req->new_response(200); $res->content_type('text/html'); $res->body('hello world'); $res->finalize(); } builder { enable "Plack::Middleware::Static", path => qr/static/, root => '.'; $app; };
こちらをapp.psgiなんて名前を付けて保存し、
$ plackup -a app.psgi
とすればサーバが立ち上がり指定されたポートにアクセスすれば「hello world」が見えるはずです。一つの.psgiファイル内で完結するように作るため少々ださいですが、
return root($req) if $req->path eq '/';
この部分でパスに対するディスパッチを行い、rootサブルーチンがコンテンツの出力を担っています。こいつに、先ほど解説した、YouTubeの再生リストからビデオを取得するロジック部分と、それをHTMLで奇麗に整形して出力する部分を加え、あとはJSで工夫するだけで希望するアプリケーションが組めそうです。
Template-Toolkit を使う
お次に扱いたいのはテンプレートエンジンです。これはパフォーマンスや記法などそれぞれ特徴があるために、皆さん好みがおありでしょうが今回は比較的柔軟な記述ができるTemplate-Tookitを使います。上記の.psgiのひな形に加えて、
use Template
してから、以下のようにrenderサブルーチンを追加し、rootサブルーチンを変更してみます。
my $template_config = { INCLUDE_PATH => './templates' }; sub root { my $req = shift; my $res = $req->new_response(200); my $message = 'hello world'; $res->body( render( 'index.html', { message => $message } ) ); $res->finalize(); } sub render { my ( $name, $args ) = @_; my $tt = Template->new($template_config); my $out; $tt->process( $name, $args, \$out ); return $out; }
そして、templatesディレクトリを掘ってその中にindex.htmlという名前で
<html> <body> [% message | html %] </body> </html>
こんな感じのテンプレートファイルを用意すると、今度はTemplate-Toolkitを使った「hello world」がでるようになると思います。ようは、Perlアプリケーション側で用意した情報が変数としてテンプレートに渡り、それをHTML(もしくはそれ以外)として出力することができるというわけですね。
さて、これでベースとなるアプリケーションサーバ部分、モデルと呼ぶほどでもないですがビデオ情報を取得し解析するロジック部分、テンプレートエンジンを使ったビューの部分ができました。なんとなくMVCっぽい感じです。では、こいつを一つのアプリに仕上げていきましょう。
仕上げ
Chekera!の細かい仕様の確認です。
- トップページにはフォームを表示
- そこに再生リストのURLもしくはIDを入力
- GETリクエストを発行するとアプリが受け取り、IDを抽出
- IDを元にYouTube APIへリクエスト
- ビデオ一覧を取得
- ビデオ情報をテンプレートへ渡す
- テンプレートは一覧情報を受け取りサムネイル表示
- jsを駆使して埋め込みプレーヤーでの再生などを行う
という具合でしょうか。てなわけでこれを叶えるためのapp.psgiファイルをどかーんと貼付けます。
use Plack::Builder; use Plack::Request; use Template; my $template_config = { INCLUDE_PATH => './templates' }; my $app = sub { my $env = shift; my $req = Plack::Request->new($env); return root($req) if $req->path eq '/'; [ 404, [ "Content-Type" => "text/plain" ], ["Not Found"] ]; }; sub root { my $req = shift; my $args; if ( my $playlist = $req->param('playlist') ) { $playlist =~ s/.*p=([^&]+).*/$1/; my $videos = playlist($playlist); $args->{videos} = $videos; } my $res = $req->new_response(200); $res->content_type('text/html'); $res->body( render( 'index.html', $args ) ); $res->finalize; } sub render { my ( $name, $args ) = @_; my $tt = Template->new($template_config); my $out; $tt->process( $name, $args, \$out ); return $out; } sub playlist { my $playlist_id = shift; require XML::Feed; my $feed = XML::Feed->parse( URI->new("http://gdata.youtube.com/feeds/api/playlists/$playlist_id") ) or return []; my @videos; for my $entry ( $feed->entries() ) { my ($id) = $entry->link =~ /\?v=([^&]+)/; push( @videos, { title => $entry->title, id => $id } ); } return \@videos; } builder { enable "Plack::Middleware::Static", path => qr/static/, root => '.'; $app; };
そして、テンプレートファイル「index.html」はShadowbox.jsというクールなスライドショーのライブラリを利用して以下のようになりました。
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Chekera!</title> <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/combo?2.8.0r4/build/fonts/fonts-min.css&2.8.0r4/build/grids/grids-min.css"> <link rel="stylesheet" type="text/css" href="static/shadowbox.css"> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script> <script type="text/javascript" src="static/shadowbox.js"></script> <script type="text/javascript"> Shadowbox.init({ players: ['swf'] }); window.onload = function(){ Shadowbox.setup("a.video", { width: $(window).width(), height: $(window).width() / 16 * 9, flashParams: { allowScriptAccess: "always" } }); }; function onYouTubePlayerReady(playerId) { ytplayer = document.getElementById("sb-content"); ytplayer.addEventListener("onStateChange", "onytplayerStateChange"); }; function onytplayerStateChange(newState) { if( newState == 0 && Shadowbox.hasNext() ){ Shadowbox.next(); } }; </script> </head> <body> <div id="doc" class="yui-t7"> <div id="hd"><h1><a href="/">Chekera!</a></h1></div> <div id="bd"> <div class="yui-g"> <form action=""> <input name="playlist" type="text" size="80" /> <input type="submit" /> </form> [% IF videos -%] [% FOREACH video = videos %] <h3>[% video.title | html %]</h3> <p> <a class="video" href="http://www.youtube.com/v/[% video.id | html -%] &enablejsapi=1&playerapiid=ytplayer&autoplay=1&fmt=35&.swf" rel="shadowbox[videos]" title="[% video.title | html %]"> <img src="http://i.ytimg.com/vi/[% video.id | html %]/hqdefault.jpg" alt="" /> </a> </p> [% END -%] [% END -%] </div> </div> </div> </body> </html>
スクショ:
http://gyazo.com/8abdddb586604d8bc39f37b0f4382d25.png
jsの部分はちょっと特殊なことをしていたりしていて詳細の解説省きますが、これでリストの動画一覧が表示されてクリックすればウイーンってウィンドウがでてきて動画が再生されるはずです!
まとめ
以上、非常に小さなWebのアプリケーションをPlackベースで作ってみました。実際にまだ試してはいないのですが、PSGI互換なわけですから、mod_perlでもFastCGIでもCGIでも動くと思います。いい具合にPlack/Plack::Requestは抽象化されているのでWebアプリの挙動がわりかし理解しやすくていいですね。これよりでかくなるともろxxx.psgiにコードを記述するのはしんどくなってきますが、簡単なアプリをxxx.psgiにゴリっと書くのもなかなか楽しいものだと思います。車輪の再開発になるかもしれませんが、こういうコードは自分の身のためになるので是非皆さんもトライしてみてください!(車輪の再開発のススメはlestrratさんが言ってましたね)
明日のCasual Trackは全裸で有名なsugyanさんです。それではEnjoy!!