tokuhirom's Blog

TinyURLをつくってみよう 〜 軽量フレームワークAmon2入門 (4) 〜

さて、今回はインストールから始めましょう。

インストール

今回は Amon2 の最新版が必要なので、すでにインストールしている場合でもあらためてインストールしてください。
環境は OSX, linux を想定していますが、Windowsでもそれほどかわらないかと思います。Windows でやっていてわからなかったら mattn さんにきいてください。

さて、Amon2 のインストールは非常に容易です。Amon2はCPANにリリースされていますから、通常のCPANモジュールと同様にcpanmコマンドでインストールできます。

 % curl -L http://cpanmin.us | perl - Amon2 Amon2::DBI

で、インストールが完了します。テストに失敗する場合には、コメント欄などできいてください。

TinyURLを作る

ぐだぐだとかたっていても何もつたわらないと思うので、チュートリアルに入りましょう。
とりあえず、簡単で実用的っぽいアプリケーションというところで、TinyURLをつくってみましょう。bit.lyみたいなあれですね。
まずは雛形を生成しましょう。雛形を生成するにはコマンドラインで以下のコマンドをうちこむべし。

% amon2-setup.pl --flavor=Lite TinyURL

そうすると、ひながたができますよ、と。出来上がったらためしに起動してみましょい。

% plackup app.psgi

さてブラウザでアクセスするとこんな感じの画面が表示されるはずです。これが Amon2::Lite のスタートアップ画面です。Amon2::Lite はシンプルなサイトをつくるためのものなのでスタートアップ画面がごくシンプルになっています。

うまくいきましたでしょうか。うまくいったらつぎに すすみましょう。
スキーマを 以下のように作ってみて、sql/sqlite3.sqlとして保存しましょう。

create table tinyurl (
    key varchar(20) primary key,
    url text
);

できたら以下のコマンドで反映させましょう。sqlite3 コマンドは各自インストールしておいてくださいね。

sqlite3 development.db < sql/sqlite3.sql

できたらば次のすてっぷです。実際にアプリをかいてみましょう。

use strict;
use warnings;
use utf8;
use File::Spec;
use File::Basename;
use lib File::Spec->catdir(dirname(__FILE__), 'extlib', 'lib', 'perl5');
use lib File::Spec->catdir(dirname(__FILE__), 'lib');
use Plack::Builder;
use Amon2::Lite;

# Amon2::Plugin::DBI をよみこむ
__PACKAGE__->load_plugins('DBI');

# 設定
sub config {
    +{
        DBI => [
            "dbi:SQLite:dbname=$ENV{PLACK_ENV}.db",
            '',
            ''
        ],
    }
}

get '/' => sub {  # / にたいする GET リクエストをフックする
    my $c = shift;
    return $c->render('index.tt'); # index.tt を描画してかえす
};

post '/create' => sub { # /create にたいする POST リクエストをフックする
    my $c = shift;
    my $src = $c->req->param('url') || return $c->redirect('/');

    my $key = sub {
        # dup check
        {
            my $key = $c->dbh->selectrow_array(q{
                SELECT key FROM tinyurl WHERE url=? LIMIT 1
            }, {}, $src);
            return $key if $key;
        };
        # create new one.
        {
            my @chars = ( 'A'..'Z', 'a'..'z', '0'..'9' );
            my $key;
            for (1..6) {
                $key .= $chars[int rand @chars];
            }
            $c->dbh->do(q{INSERT INTO tinyurl (key,url) VALUES (?, ?)}, {}, $key, $src);
            return $key;
        }
    }->();
    return $c->render('result.tt', {tinyurl => $c->req->base . 'g/' . $key});
};

get '/g/{key}' => sub {
    my ($c, $args) = @_;
    my $key = $args->{key} || die "oops";
    my $url = $c->dbh->selectrow_array(q{
        SELECT url FROM tinyurl WHERE key=? LIMIT 1
    }, {}, $key);
    if ($url) {
        warn $url;
        return $c->redirect($url);
    } else {
        return $c->res_404();
    }
};

builder {
    enable 'Plack::Middleware::Static',
        path => qr{^(?:/static/|/robot\.txt$|/favicon.ico$)},
        root => File::Spec->catdir(dirname(__FILE__));
    enable 'Plack::Middleware::ReverseProxy';

    __PACKAGE__->to_app();
};

__DATA__

@@ index.tt
<!doctype html>
<html>
<head>
    <met charst="utf-8">
    <title>TinyURL</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
    <h1>tinyurl</h1>
    <form method="post" action="/create">
        <input type="text" name="url" />
        <input type="submit" value="tiny!" />
    </form>
</body>
</html>

@@ result.tt
<!doctype html>
<html>
<head>
    <met charst="utf-8">
    <title>TinyURL</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
    <h1>tinyurl</h1>
    <div>[% tinyurl %]</div>
    <a href="/">back to top</a>
</body>
</html>

ブラウザをリロードしてみると、うまいこと表示されているかとおもいます。
コードに不明瞭な点はあまりないかと思います。いわゆる Sinatra 風のフレームワークです。

$c ってなんだ

$c というのはコンテキストオブジェクトです。Amon2::Web を継承したクラスです。

Amon2::Lite においてはこれが app.psgi のファイルのパッケージになっています。ですから、sub config { ... } というメソッドを定義すれば、$c->config() でよびだせます。

__PACKAGE__->load_plugin('DBI');

これで、Amon2::Plugin::DBI をロードしています。Amon2 はプラグイン機構によって無限の拡張性を演出しております。このプラグインをロードすることにより $c->dbh() というメソッドが追加され、DB へ簡単にアクセスできるようになります。このメソッドは Amon2::DBI のインスタンスをかえします。Amon2::DBI は DBI でいうところの $dbh です。DBI の $dbhでよべるメソッドは基本的にすべてよべますが、さらにそこにいくつかの便利な機能が追加されています。詳細は今後くわしく解説する予定です(なげださなかった場合)。

ルーティングは Router::Simple

get() と post() はディスパッチャにたいする登録をおこなう関数です。Amon2 においてディスパッチャというのは HTTP リクエストの具合によって実際に処理をおこなうコードをふりわける人のことです。ここで登録していない URL にアクセスすると 404 になってしまいます。引数は $c です。このオブジェクトから $c->req(リクエストオブジェクト。Amon2::Web::Request のインスタンス) などが取得できます。また、$c->redirect($url) でリダイレクト用の Amon2::Web::Response のインスタンスを生成したり、$c->render('index.tt') で Text::Xslate というテンプレートエンジンをつかって Amon2::Web::Response のインスタンスを生成したりできます。

ルーティングルールはちょっと特殊な記法を採用しています。この部分は Router::Simple というライブラリを使用しています。Router::Simple をかいたのも私です。Router::Simple についてはあとでくわしく解説しますが、先にしりたい方は Router::Simple のドキュメントを参照してください。

DATA セクションからのデータとりだしは Data::Section::Simple

__DATA__ の下にテンプレートが配置されています。この部分は Data::Section::Simple というライブラリをつかってファイルをとりだして、Text::Xslate にわたしています。各ファイルは@@ でくぎられています。

だいたいまあこんなところです。わからないところがあったらコメントで指摘してくださいね。

まとめ

Amon2::Liteを利用してTinyURLを構築してみました。

とりあえず、つくってみたってかんじでこまかい解説がぬけているので、次回以後は個々のぶぶんについて解説していきます。