天然パーマです。

sinatra ライクな micro WAF「Hitagi」を作ってみた

MojaMojaを使ってみてsinatraライクな micro web application framwork に興味が湧いたので、 本家sinatraやMojolicious::Lite、Schenker、Dancerあたりを参考にして 自分なりの micro WAF を言わば「再開発」してみた。 あくまでオレオレ。名前はアニメ化物語がインスパイアで「Hitagi」と言います。

Hitagi - Shall we talk about stars and micro web application frameworks.

日本人の方が(メインの)作者のCPANモジュールを組み合わせてsinatraっぽくかつ簡単な モデルまでサポートさせているのが特徴です。 以下のようにモジュールを使っています。おかげでHitagiのコード量は200行弱に収まりました。

  • Plack::Request/Response - リクエスト/レスポンス生成
  • Text::MicroTemplate + Data::Section::Simple - ビュー
  • Router::Simple - ディスパッチャ
  • DBIx::Skinny - モデル(DB)

簡単な使い方、SYNOPSISがこちら。モデルが戦場ヶ原ひたぎなので最後に star; と書きます。

 use Hitagi;
get '/' => sub { render( 'index', { message => 'Hi' }) };
star;

__DATA__

@@ index
<h1>message : <?= $message ?></h1>

これをmyapp.plとして保存して perl コマンドで実行すればデフォルト5000番ポートでサーバが立ち上がります。

 $ perl myapp.pl

getメソッドでディパッチとそれに対するのコントローラの定義をします。 「__DATA__」以下のデータセクションにText::MicroTemplateの書式でテンプレートを書きます。 renderメソッドがそのテンプレートの処理をしてくれます。 第一引数にテンプレートの名前、第二引数にテンプレートに渡したい値やサブルーチンをハッシュリファレンスの 形式で渡してあげます。するとハッシュのキーの名前を変数名としてそのキーに対する値やサブルーチンをテンプレートの中で扱うことができるようになります。(Text::MicroTemplate::Extendedからパクりました^^)

テンプレートの扱いをちょっと便利にしていて、 「layout」を使いまわすことが可能です。 こう書くと index テンプレートを render すると layout テンプレートでラップしてくれます。

 $ perl myapp.pl
use Hitagi;

...;

__DATA__
@@ index
<h1>welcome</h1>

@@ layout
<html>
</head><title>title</title></head>
<body>
<div id="container">
    <?= content ?>
</div>
<address>This content is made by Hitagi</address>
</body>
</html>

静的ファイルのサーブもstaticというディレクトリに置けばよいとデフォルトで利用可能で、 またモデルとしてDBもサポートしています。 詳しくはREADMEやPODを参照してもらいたいのですが、 手っ取り早くわかるために tokuhirom の OreOre-NoPasteの仕様を真似て似た様なものを作ってみたのでそのコードを掲載します。

 use Hitagi;
use Data::UUID;

my $uuid_gen = Data::UUID->new;

set db => {
    connect_info => [ 'dbi:SQLite:','', '' ],
    schema       => qq{
        install_table entry => schema {
           pk 'id';
           columns qw/id body/;
        };
    }
};

db->do(q{CREATE TABLE entry ( id varchar, body text )});

get '/' => 'index';

post '/post' => sub {
    my $req  =  shift;
    my $body  = $req->param('body') or redirect( $req->base );
    my $uuid  = $uuid_gen->create_str;
    db->insert(
        entry => {
            id   => $uuid,
            body => $body,
        }
    );
    return redirect( $req->base . "entry/$uuid" );
};

get '/entry/{entry_id}' => sub {
    my ( $req, $args ) = @_;
    my $entry_id = $args->{entry_id};
    my $entry = db->single( entry => { id => $entry_id, } );
    return res(404,[],'Not Found')->finalize unless $entry;
    render( 'entry', { body => $entry->body } );
};

star;

__DATA__

@@ layout
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
  <title>nopaste</title>
  <link rel="stylesheet" type="text/css" href="<?= $base ?>static/screen.css" />
</head>
<body>
<div class="container">
  <h1><a href="<?= $base ?>">Yet Another nopaste</a></h1>
  <?= content ?>
</div>
</body>
</html>

@@ index
<form action="<?= $base ?>post" method="post">
<p><textarea name="body" cols="60" rows="10"></textarea></p>
<p><input type="submit" value="no paste" /><p>
</form>

@@ entry
<pre>
<?= $body ?>
</pre>

こうすれば nopaste.pl と static/screen.css だけあればとりあえずは 動くものができちゃいます。 一応デプロイのことも考えていて、nopaste.pl を nopaste.cgi にするように 拡張子によってCGIとして動作させることができます。 また、もちろんPlackベースなのでPSGIアプリとしても利用可能で、 今のところ $ENV{PLACK_ENV} の値をみて star メソッドがアプリケーションハンドラを返すかどうかの 分岐をしています。

一発ネタの内部的には簡単なアプリを今までわざわざCatalystで作ってたりしましたが、 今度からHitagiベースで開発していこうかと思います。 個人的な要望含め今後改善していくので、まぁ似た様なものありますが、気になった方は試してみてください。 ということでEnjoy! ダウンロード&githubレポジトリは以下から!