Mojoliciousのデフォルトのセッションではcookieにbase64化した文字列を格納しています。そのため、4kbの制限や毎回すべてのセッションデータを送ることになったりと色々よろしくないところがあるんですが、FW基盤としてのMojoliciousという意味ではプラグインでどうにでもなるような構造が用意されていればどうでも良い気がする。
そんなsessionに関して、使う機会があったので少しでもまともな実装を探してみました。
Plack::Middleware?Mojolicious::Plugin?
PerlのWAFはMojolicious推しな件とそのノウハウ – ゆーすけべー日記
でも書かれている通り、デフォルトのsession管理は使えない。また、この中ではPlackのセッション機構を使っている。個人的に、環境依存ファイルは極力少なくしたかったので可能な限りMojolicious::Plugin::Configで利用している設定ファイルのみに留めておきたかった。
ただ、Mojoliciousで利用している設定ファイルと共通化したいというのも下記のMojolicious::Plugin::PlackMiddlewareというのを使えばもしかしたら解決できるのかもしれない。ただ、試していないのでなんとも言えないが。
Mojolicious::Plugin::PlackMiddleware – search.cpan.org
MojoX::Session
今回、初めてMojoliciousを使うということもあって標準的なものを使おうと色々探してみたところ、MojoX::Sessionというのを見つけました。
MojoX::Session – search.cpan.org
データストアとしてRedisを使っていたので当然MojoX::Session::Store::Redisを使うことにしました。実際に使ってみると・・・。
全然動かねぇ!!!
なんで動かないのか調べてみると、Redisのデータとして「Hash(x14123)」みたいな文字が入っていました。もしや・・・と思い、実装を見てみると、hashrefをそのまま格納してやがりました。RedisなんでせめてHash型として入れてくれていれば最低限動いていたんですが、全く動かない程度の実装になっていました。
MojoX::Session::Storeの方でbase64など暗号化した文字列を下位モジュールへ渡してくれると思いきや、MojoX::Sessionの実装方針がわかりませんが、同梱されているMojoX::Session::Store::dbiの実装を見てみるとMojoX::Session::Store::dbiの中でbase64化していました。
これは、MongoDBなどコレクションに格納する際にはデータを構造的に入れれるためへの配慮なんだろうと思い、Redisの方ではjson化したものを突っ込むようにしました。本家の方にPull Requestは送ったんですがまだ取り込まれる気配はありません。
現在までの実装はgithubへアップしています。
fukata/p5-MojoX-Session-Store-Redis
json化するのとRedisのキー自体にもexpireを適用するようにも対応しています。
インタフェース
無事、データストアにsessionを保存することができるようになったので、実際にコントローラ内部で使ってみたいと思います。MojoX::Sessionを使う場合、startupの中で下記のように定義します。
$self->plugin( session => { stash_key => 'mojox-session', store => MojoX::Session::Store::Redis->new( { server => '127.0.0.1:6379', redis_prefix => 'mojo-session', redis_dbid => 1, } ), expires_delta => 3600, } ); |
そうするとコントローラ内ではこんな感じになります。
my $session = $self->stash('mojox-session'); $session->load; $session->create unless $session->sid; #set $session->data( id => 5, name => 'hoge', ); #get my $name = $session->data('name'); |
Helper
sessionを取り出すのにstash関数を直に使うのもダサいし、リクエスト毎に自分でloadしたりするのもなんだかなぁと思ったので簡単なHelperを実装しました。
use strict; use warnings; use utf8; package Hoge::Mojolicious::Plugin::SessionHelper; use Mojo::Base 'Mojolicious::Plugin'; sub register { my ($self, $app, $conf) = @_; $conf ||= {}; $conf->{stash_key} ||= 'mojox-session'; my $stash_key = $conf->{stash_key}; $app->hook( before_dispatch => sub { my $self = shift; my $session = $self->stash( $stash_key ); $session->load; $session->create unless $session->sid; $self->stash() } ); $app->helper( sessions => sub { shift->stash($stash_key); } ); } 1; |
これで、コントローラ内部ではこんな感じで書けるようになりました。
#set $self->sessions->data( id => 5, name => 'hoge', ); #get my $name = $self->sessions->data('name'); |
リクエスト毎にloadなどもしないで良いし、セッション用のAPIも用意することで見た目もすっきりするようになりました。本当は$self->session->data(‘name’)みたいな感じで書けると良かったんですがsessionメソッドはMojolicious::Controllerに既に定義されているためできませんでした。
MojoX::Sessionも元々存在するMojolicious::Controller::sessionメソッドは利用していないので、フックしにくい構造になっているのかもしれない。せっかくデフォルトでAPIを定義しているのだからこれを上手く利用したいし、その方がわかりやすいだろう。
その辺の話はまた今度。今回はとりあえずこの実装でやってみる。
追記:2012.11.14
MojoX::Session::Store::Redisが動くようになりました!でMergeされて無事動くようになりました。