ゼロからはじめるAmon2

自己紹介

イントロダクション

初心者向けです

WAFとは

Web Application Framework

PerlのWAFといえば

PerlのWAFといえば

なるべく学ぶことは少なくしたい

再入門に時間なんてかけてられない

軽量フレームワークがいい

Perlにおける軽量WAF

Amon2

PrePANがAmon2を使っているみたいです。

https://github.com/prepan-developers/prepan

インストール

% cpanm Amon2

スケルトンを作成する

% amon2-setup.pl MyApp
% cd MyApp
% plackup app.psgi
% tree
.
├── Makefile.PL
├── app.psgi
├── config
│   ├── deployment.pl
│   ├── development.pl
│   └── test.pl
├── db
│   └── development.db
├── lib
│   ├── MyApp
│   │   ├── Web
│   │   │   └── Dispatcher.pm
│   │   └── Web.pm
│   └── MyApp.pm
├── sql
│   ├── mysql.sql
│   └── sqlite.sql
├── static
│   ├── 404.html
│   ├── 500.html
│   ├── 502.html
│   ├── 503.html
│   ├── 504.html
│   ├── bootstrap
│   │   ├── bootstrap-dropdown.js
│   │   ├── bootstrap-tooltip.js
│   │   └── bootstrap.css
│   ├── css
│   │   └── main.css
│   ├── img
│   ├── js
│   │   ├── jquery-1.7.2.min.js
│   │   └── main.js
│   └── robots.txt
├── t
│   ├── 00_compile.t
│   ├── 01_root.t
│   ├── 02_mech.t
│   ├── 03_assets.t
│   ├── 06_jslint.t
│   └── Util.pm
├── tmpl
│   ├── include
│   │   ├── layout.tt
│   │   └── pager.tt
│   └── index.tt
└── xt
    ├── 02_perlcritic.t
    └── 03_pod.t

15 directories, 34 files

ディレクトリ構造

├── app.psgi
├── config
│   ├── deployment.pl
│   ├── development.pl
│   └── test.pl
├── db
│   └── development.db
├── lib
│   ├── MyApp
│   │   ├── Web
│   │   │   └── Dispatcher.pm
│   │   └── Web.pm
│   └── MyApp.pm
├── sql
│   ├── mysql.sql
│   └── sqlite.sql
└── tmpl
    ├── include
    │   ├── layout.tt
    │   └── pager.tt
    └── index.tt

amon2-setup.pl

スケルトンを作成します

% amon2-setup.pl --flavor=Basic MyApp

flavor

Amon2の基本

$c

context

get '/' => sub {
    my ($c) = @_;
    my $foo = $c->req->param('foo');
    return $c->render('index.tt' => {
        bar => 'bar',
    });
};

request

Amon2::Web::Request

Plack::Requestを継承したもの

get '/' => sub {
    my ($c) = @_;
    $c->req; # Amon2::Web::Request
    my $page = $c->req->param('page');

    ...
};

response

Amon2::Web::Response

中身はPlack::Responseと変わりません

get '/' => sub {
    my ($c) = @_;
    return $c->create_response(
        200,
        ['Content-Type' => 'text/plain'],
        ['Hello!']
    );
};

config

$c->config->{'DBI'};

PLACK_ENVでconfigが切り替わります

% plackup -E deployment app.psgi

dispatcher

Router::Simple

スケルトン

lib/MyApp/Web/Dispatcher.pm

package MyApp::Web::Dispatcher;
use strict;
use warnings;
use utf8;
use Amon2::Web::Dispatcher::Lite;

any '/' => sub {
    my ($c) = @_;
    $c->render('index.tt');
};

1;
get '/' => sub {
};

post '/' => sub {
};

any '/' => sub {
};
get '/entry/{entry_id}' => sub {
    my ($c, $args) = @_;
    $args->{entry_id};
};

view

Text::Xslate

get '/' => sub {
    my ($c) = @_;
    return $c->render('index.tt' => {
        foo => 'bar',
    });
};

基本的にはこんな感じです

plugin (1)

plugin (2)

実際にウェブアプリを作ってみる

1) オレオレGyazo

使用するモジュール

% amon2-setup.pl Gyazo

Gyazoの仕様

今回はdispatcherにベタ書きします

post '/api/upload' => sub {
    my ($c) = @_;
    my $image = $c->req->upload('imagedata') or die;
    die unless $image->size < 5 * 1024 * 1024; # filesize is less than 5MB.

    my $imagedata = do {
        open my $fh, '<', $image->path or die $!;
        local $/; <$fh>;
    };
    die unless $imagedata =~ /^\x89PNG\x0d\x0a\x1a\x0a/; # .png format only.

    my $hash = md5_hex($imagedata);
    my $filename = File::Spec->catfile($c->base_dir, 'dat', "$hash.png");
    unless (-e $filename) {
        open my $fh, '>', $filename or die $!;
        print {$fh} $imagedata or die $!;
    }

    my $image_url = $c->req->base . "dat/$hash.png";

    return $c->create_response(200, [], [$image_url]);
};

app.psgi

builder {
    enable 'Plack::Middleware::Static',
        path => qr{^(?:/static/|/dat/)},
        root => File::Spec->catdir(dirname(__FILE__));

完成

これだけだと面白くないので短縮URL機能もつけてみます

sql/sqlite.sqlを書き換えます

CREATE TABLE IF NOT EXISTS gyazo (
    image_key CHAR(6) PRIMARY KEY,
    url       TEXT
);
    my $key = sub {
        # dup check
        {
            my $key = $c->dbh->selectrow_array(q{
                SELECT image_key FROM gyazo WHERE url=? LIMIT 1
            }, {}, $image_url);
            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 gyazo (image_key, url) VALUES (?, ?)}, {}, $key, $image_url);

            return $key;
        }
    }->();

    my $tiny_url = $c->req->base . $key;
    return $c->create_response(200, [], [$tiny_url]);

短縮URLにアクセスしたら画像のURLにリダイレクトするようにします

get '/{key}' => sub {
    my ($c, $args) = @_;

    my $image_url = $c->dbh->selectrow_array(q{
        SELECT url FROM gyazo WHERE image_key=? LIMIT 1
    }, {}, $args->{key});

    if ($image_url) {
        return $c->redirect($image_url);
    } else {
        return $c->res_404();
    }
};

ちゃんと動くかな

おまけ: KENT WEB

#!/usr/bin/env perl

#┌─────────────────────────────────
#│ LightBoard : light.cgi - 2012/03/25
#│ Copyright (c) KentWeb
#│ http://www.kent-web.com/
#└─────────────────────────────────

# モジュール宣言
use strict;
use CGI::Carp qw(fatalsToBrowser);
use lib "./lib";
use Jcode;

# 設定ファイル認識
require "./init.cgi";
my %cf = &init;

# データ受理
my %in = &parse_form;

# 処理分岐
if ($in{mode} eq 'find') { &find_data; }
if ($in{mode} eq 'note') { &note_page; }
if ($in{mode} eq 'past') { &past_page; }
if ($in{mode} eq 'del_log') { &del_log; }
&bbs_list;

Amon2で書き直しましょう

使用するモジュール

ちゃんと動くかな

まとめ

今回はAmon2を紹介しました。

Amon2は非常にシンプルなWAFで、使い勝手がいいので再入門におすすめです。

とはいっても、WAFって好みだと思うので自分に合ったWAFを使うのが一番だと思います。

Amon2はコードも読みやすいし、複雑なことをしていないので個人的におすすめです。

質問があれば

ありがとうございました