MojoliciousでPocketIOを使いつつセッションを共有する
さて念願の81忘年会、2次会まではよかったのですが、3次会は飲み過ぎてダウンしてました。 日曜日は久々に二日酔い。グロッキーで記事を書くつもりが…orz
というわけで、Goomerの技術的な話を、備忘録的にまとめて書いておきたいと思います。元記事はイベント内Likeツール、Goomer作りましたです。
Goomerは、HTML5のWebSocketを使ったリアルタイムWebアプリケーションです。 WAFはMojoliciousを使っています。Mojoliciousには標準でWebSocket実装が乗っているのですが、今回は、socket.ioのperl実装であるPocketIOを使っています。
あまりよく理解していなかったのですが、実際に実装をしてみるまで、WebSocket = socket.ioだと思っている僕がいました。 なので最初はMojoliciousに載っていたWebSocketを使って色々やってました。ただ、socket.ioはWebSocketが利用できないクライアントのときのフォールバック実装が豊富にあり、IE5.5からサポートしているという変態さを発揮しています。ヤバ過ぎる。Mojoliciousの方はそんなのなくて、WebSocket実装なくんばブラウザにあらず的な高慢さです。
いやまぁ、正直IE5.5とか…
とにかく、まぁ、そういうわけでMojoliciousのWebSocket実装から、PocketIOへと移行しました。そして、Mojolicious側とPocketIO側で同じセッション情報を利用したかったので、色々工夫したわけですよ。
yusukebeさんの「PocketIOのイカ娘語echoサンプル」がMojolicious + PocketIOなので参考になりそうです。が、正直perlはじめて1ヶ月経たない僕にはいろいろと理解ができず…。
特に、MojoliciousとPocketIOでどうやったらセッションを共有できるのか、また、よく色々なところに「Mojoliciousのセッション機能は貧弱だからPlackのSession使いましょう」とか書いてあるんだけど、そのPlackのセッションはどうやって使うのかと!
諦めムードですよ。まじで。
そんなわけで、だらだら前置き長いですが、 「MojoliciousとPocketIOを同じアプリケーションで作成しつつ、MojoX::Sessionを使ってセッションを共有する」 サンプルをgithubのリポジトリにおきました。
perl始めて2ヶ月だし、ほぼやっつけなのでソースコードの汚さはどうか!と先に言っておきます。 以下解説です。
Mojoliciousアプリを作る
まずは、普通に
mojo generate app [app_name]
としてアプリを生成します。次に自動生成のscript/app_name
とは別に、PSGIファイルを作ります。
PSGIファイルの書き方
mojolicio.usでPocketIOを使うためにはPSGIファイルを書き換えないといけないのですが、やりかたが分からず…。
CookBookよんでも、正直分からない…
こういうのみなさんどうやって身に付けてるんだろう。
とりあえず、色々なサイトぐぐって片っ端から色々やるに、script/[app_name].psgi
ファイルは次のようになりました。
script/mojolicious_pocket_io.psgi
MojoX::Sessionを使う
use utf8; use warnings; use strict; use Mojo::Server::PSGI; use Plack::Builder; use Plack::App::File; use PocketIO; use FindBin; use lib "$FindBin::Bin/../lib"; use MojoliciousPocketIO; use MojoliciousPocketIO::WebSocket; #MojoliciousをPSGIで my $psgi = Mojo::Server::PSGI->new( app => MojoliciousPocketIO->new ); my $app = sub { $psgi->run(@_) }; #socket.ioのスクリプトとファイルのルートディレクトリ my $siroot = "$FindBin::Bin/../public/js/"; builder { #socket.ioのスクリプトとファイルをmountする mount '/socket.io/socket.io.js' => Plack::App::File->new(file => "$siroot/socket.io.js"); mount '/socket.io/static/flashsocket/WebSocketMain.swf' => Plack::App::File->new(file => "$siroot/WebSocketMain.swf"); mount '/socket.io/static/flashsocket/WebSocketMainInsecure.swf' => Plack::App::File->new(file => "$siroot/WebSocketMainInsecure.swf"); #PocketIOをマウント mount '/socket.io' => PocketIO->new( class => 'MojoliciousPocketIO::WebSocket', method => 'run' )\ ; #Mojoliciousをマウント mount '/' => $app; };
のような感じになりました。
知っている人にはなんでもないんでしょうが、mountっていうのでアプリケーションをパスを振り分けることができるんですね。
あと、PocketIOのサイトをいくら探してもsocket.io.js
とswfファイルを見つけることができなかったのですが、githubのsocket.io-clientにあるんですね。ものすごい探した…orz。
まぁ、これでMojolicio.usを使った普通のアプリケーションと、PocketIOを使ったWebSocketアプリケーションが両立できます。
で、問題はここから。
MojoliciousのセッションとPocketIOのセッションを透過的に扱いたい!
というわけで、MojoX::Sessionを使いました。Plack::MiddleWare::Sessionを使ってみたかったのですが、なんかよくわからなかったのでMojoX::Sessionです。一応Mojoliciousのプラグインもあるようなので。
Mojolicious側のセッションは次のように初期化しています。lib/MojoliciousPocketIO.pm
sub setup_app{ my $self = shift; my $config = $self->plugin('Config', {file => $self->app->home->rel_file('conf/app.conf')}); $self->secret($config->{secret}); $self->controller_class('MojoliciousPocketIO::Controller'); my $handler = DBIx::Handler->new( $config->{db_dsn}, $config->{db_username}, $config->{db_password}, +{ mysql_auto_reconnect => 1, mysql_enable_utf8 => 1, RaiseError => 1, PrintError => 0, AutoCommit => 1, on_connect_do => [ "SET NAMES 'utf8'", "SET CHARACTER SET 'utf8'", ], }, ); $self->plugin( session => { stash_key => 'mojox-session', store => [dbi => {dbh => $handler->dbh}], transport => 'cookie', expires_delta => 1209600, #2 weeks. init => sub{ my ($self, $session) = @_; $session->load; if(!$session->sid){ $session->create; } }, } ); }
みたくなってます。Handlerの初期化とかは、なんかもっといい方法あると思います。で、WebSocket側は、
WebSocketController.pmにソースコードがあります。Mojolicious側と大体同じ方法でMojoX::Sessionを初期化しています。
その際、session_idが必要なのですが、
WebSocket.pmでPocketIOのENVからHTTP_COOKIEを取り出し、SIDを取得しています。
sub session_id{ my $self = shift; my $socket = shift; my $cookie = $socket->{conn}->{on_connect_args}[0]->{HTTP_COOKIE}; if($cookie =~ /sid=([0-9a-f]+)/){ return $1; } return undef; }
SIDさえ取得できれば、
$session->load($self->session_id);
として、Mojolicious側のsessionを取得することができます。
いやしかし、$socket->{conn}->{on_connect_args}[0]->{HTTP_COOKIE};
というのを発見するのに1日かかった感じです…orz
ここまでくれば、Mojolicious側のMain.pmで
sub set_name{ my $self = shift; my $name = $self->param('name'); $self->session(name => $name); $self->render(json => $name); }
みたいな感じで、sessionにセットしたnameを、
sub onEcho{ my ($self, $socket, $message) = @_; my $room = $self->session('room_id'); my $name = $self->session('name'); $socket->sockets->in($room)->emit('echo', $name . ' says ' . $message); }
のようにして、使うことができます。
分かっちゃえば楽だし、もう考えなくてすむのでいいんですがね…長かった。
サンプルを動かす
サンプルは
git clone https://github.com/kent013/MojoliciousPoketIO.git cd MojoliciousPocketIO
でリポジトリを取り出して、
cpanm --installdeps .
依存ライブラリをインストールし
twiggy script/mojolicious_pocket_io.psgi -l :3000
として、サーバを立ち上げた後、
http://localhost:3000
にアクセスすれば動作確認ができます。
しょかん
ショージキ、辛かった。
っていうか、この方法があっているのかも、分かりません!
もっとスマートな方法があったら教えてください!
イベント内Likeツール、Goomer作りました
もうね、ジョーの心境ですよ。
81忘年会というイベントが毎年開催されています。
1981年生まれの僕は、初開催の時から行きたくて行きたくて仕方なかったのです。
だけども、20歳からの10年間、北海道・名古屋とすんでいた僕は参加できずにいました。
東京帰ってきたから行くぞと。楽しみは楽しみなんだけど、呑んだら死にそうな状態であります!
がんばれ!
というわけで、
- 企画:id:uzullaさん
- デザイン:id:kkotaro0111さん
- プログラミング:id:kent013とid:tama_d
で、イベントで使えるWebアプリケーションを作りました。
「Goomer」
といいます。http://g.cfe.jpで公開しています。
もっと気軽に簡単にイベント内でコミュニケーションをする方法はないか!
というuzullaさんの思いを62%くらい表現できているのではないかと思います。
インタフェースはこんな感じで、Goom!ボタンを連打したり、テキストでメッセージを送ったりできます。
メッセージはtwitterにも流れます。
ボタンを押すと、リアルタイムに相手に通知されます。WebSocketを使っています。
名前の横にある#20とかの数字は、id:yusukebeさんの81忘年会用自己紹介ツールの番号です。
結構、急ごしらえなので後の方の番号の人は入ってません…あしからず。
イベントのタイムラインはこんな感じで、イベント参加者のテキストを一覧できます。
81の紹介ツール、Twitterへ移動できるので、自分にメッセージを送ってくれた人や、番号は知っているけど名前は知らない人をフォローしたりしなかったりしましょう!
とりあえず、もう移動しないと遅刻なので、行きます。
実装的には言語はperl、Mojolicious + PocketIOで実装しています。
WebSocketに色々苦労したので、その辺の技術エントリはまた今度!
逝って参ります!
mojoliciousでOAuth2
Hi, perl apprenticeなkent013です。
Mojolicious::Liteは何となく1ファイルに全て押し込む的な発想(単に慣れてないだけ)が嫌いなので、Mojoliciousを単体で使ってみようとか思ってます。
だけど、その辺に落ちているコードはLite用で苦労してます。
ま、勉強っつーコトで。
掲題のOAuth2ですが、mojolicious素敵だからさくっとできるんだろうなー。凄いんだろうなー!
って期待してやってみましたが、Railsのomniauthほどではないけど、簡単。
1, Mojolicious::Plugin::OAuth2をインストール
Mojolicious::Plugin::OAuth2をインストールするのに、
cpanm Mojolicious::Plugin::OAuth2
ってしたら、
FAIL Installing Mojolicious::Plugin::OAuth2 failed.
とか言われちゃって、Issue上げそうになりましたが、
mojo version
したら、マイナーバージョンが違ったのでアップデートしたら直りました。あぶねぇあぶねぇw
2, startupでpluginの読み込み
libの[AppName].pmのstartupに、
$self->plugin('o_auth2',
facebook => {
key => '[APP Key]',
secret => '[APP Secret'
});
と書いておく。Liteだと、
plugin 'o_auth2',
facebook => {
key => '[APP Key]',
secret => '[APP Secret]'
};
って、書くみたい。大分違うので戸惑う…本当はconfファイル書くべきなんだろうけど、まだよく分からないので、ペンディング。
3, ルーティング
bridgeするなら、
my $r = $self->routes;
$r->get('/auth')->to('auth#auth');
$r->get('/login')->to('auth#login');
$r->get('/logout')->to('auth#logout');
$r = $r->bridge('/')->to(cb => sub {
my $self = shift;
if ($self->session('login_name')) {
return 1;
}
else {
$self->redirect_to('/login');
}
});
$r->get('/')->to('main#top');
のように、bridgeの前にlogin/logout/authを書く。authはfacebook authしてくれる奴。
名前は適当。Liteだとunderとか使うのがいいみたい。
本当は、bridgeの中でsessionがinvalidだったらreturn 1するみたいな処理も書かないといけないとおもう。
4, 認証周り
sub auth {
my $self = shift;
$self->get_token('facebook', callback => sub {
my $token = shift;
my $ua = Mojo::UserAgent->new;
my $me = $ua->get(
'https://graph.facebook.com/me?access_token=' . $token)->res->json;
$self->session('login_name' => $me->{name});
$self->session('token' => $token);
$self->redirect_to('/');
});
}
sub login {
my $self = shift;
$self->session(expires => 1);
}
sub logout {
my $self = shift;
$self->session(expires => 1);
$self->redirect_to('/login');
}
logoutはセッション消してからloginに転送するだけ。 loginはログインっぽい表示するページ authはfacebookにログインしてsessionにデータ書き込んで/へ転送する。
get '/auth' => sub {
my $self=shift;
$self->get_token('facebook',callback=>sub {
...
});
};
とあって、作者のMarcusさんのEasily integrating your Mojolicious app with Facebook.という記事には、
get 'hello' => sub {
My $self=shift;
#redirects the gets the token asynchronous
$self->get_token('facebook', callback => sub {
my $token=shift;
my $me=$self->client->get(
'https://graph.facebook.com/me?access_token='.$token)->res->json;
$self->render( text =>
"Hello ".$me->{name} );
});
}
とあるんだけど、この$self->client
がそんなメソッドないぜ!っつって怒られちゃう。
むがー!!!ってなってたら、最近のmojoliciousでは、UserAgent
を使うように変更されたそうで。
Mojo::Client has been deprecateでRiedelさんがいってはりました。
5, しょかん
というわけであとは、facebookでAPIキーとれば動きます。
かなり簡単。イロイロ外してるところはあるかもしれませぬが。
でもサポートしてるサービスが少ないのが玉に瑕かな。まー、追々ということで。
OSXでperl実行環境を作ってyanchaを動かす。
ちょっと落ち着いたのでブログ。
もう10日経ったのか...!!!って感じです。
いまさらかよ!って感じですが、自社でチャットツールを作ろうとしています。
その参考として@uzullaさんたちがhachioji.pmのメンバで作っているyanchaをインストールしようかと思って。
yancha (旧名yairc) はログの保全、リアルタイム性、モバイル端末への対応を重視した、Perlで作成されたチャットシステムです。
サーバサイドはPerl、クライアントサイトはHTML+JSで作成され、PocketIOによりWebsocketで通信しています。
https://github.com/uzulla/yancha より
で、ほぼ生粋のぺちぱーであまのじゃくな僕は、mongerのみなさんにバカにされつつも、PHPにこだわってきた個人的な恨み経緯があるので、ちょろーっと触った事があるくらいなんですよね、perl。
まぁ、そういうわけで、環境構築から頑張らなきゃいけないなーって気が重かったんだけど、マジで簡単っす。
世の中進歩してるもんだ。こういう地道な改善、大変だよな。頭が下がる。
あ、ちなみにOSXでやってます。
1, perlbrew
perlbrewは、perlのrvmみたいなもんで、pythonのpython_selectですかね。
PHPってそんなんあったっけ?いつから、世の中こんなに便利になったんだろう。
インストールはperlbrew + cpanminus + local::lib で環境構築あたりを見てもらったりすればいいのではないかと。 で、インストールできたら、
perlbrew list
とタイプすると、perlbrew経由でインストールしたバージョンがでます。
perlbrew switch perl-5.16.1
とすればバージョンが切り替わります。
後でインストールするライブラリとか、OSX標準のperlだとエラーになったりするので、お気をつけ。
2, cpanminus
で、cpanm。perlのパッケージマネジャ。gemとかpearみたいなもんだろうと。
cpanはよりもスリムアップして使いやすくなったからcpanmというらしい。
perlモジュールのinstallにcpanmを使うをみればインストールできます。
3, Plack
PlackはPSGI(Perl Server Gateway Interface)のリファレンス実装だそうです。
JavaのServlet APIとTomcatみたいなもんかな。
で、これをインストールするには
cpanm Task::Plack
でオッケー。Lionでエラーになるとかいうのがあったけど、もうパッチあたってて大丈夫だった。
4, MySQL
まぁ、適当に入れればいいと。僕はportsでインストールしました。
port install mysql55 +server
あと、workbenchをわすれずに。
workbench動かす時は、パスの設定が重要。
How to setup MySQL Workbench to work with MacPortsが詳しい。
ただしパスが/macportsになっているので、/opt/localにすべし。
sudo launchctl load -w /Library/LaunchDaemons/org.macports.mysql5.plist
すると自動起動する。
5, DBD::mysql
あと、データベースの抽象化レイヤ入れる。
cpanm DBD::mysql
で終わり。ただし、実行前に、 PATHに
/opt/local/lib/mysql55/bin/
が入っていることを確認すること。mysql_configが見当たらなくておこられる。
6, Yancha
本題。githubのリポジトリから、適当な場所に
git clone https://github.com/uzulla/yancha/
して、yanchaディレクトリに移動し
cpanm --installdeps .
すればインストール終了。簡単過ぎる…何か罠があるのでは…
あとは、yanchaディレクトリにある、start.shを動かすか
twiggy -l :3000 chat.psgi
ってして、http://localhost:3000にブラウザでアクセスすればOKです。
Twitter使う場合とかは、config.pl読んでね。
しょかん
perlのセットアップすると、「miyagawaさん、やべぇ…」ガクブルってなるねw