Akihiro's Programmer Blog

Technology Notes for Personal

Amon2の流れを掴む - 1

 Amon2のBasicフレーバーを題材に、どんなことが行われているのか丁寧に見ていきます。

起動コマンド "carton exec perl -Ilib script/sample-server"

  • carton execコマンドは、Cartonでインストールした./local以下のモジュールを使うということ。これで他アプリとのモジュールの依存関係を解消出来ます。全てこのアプリ内で完結して、良い感じです。

  • -Ilib-I libと同じ意味です。perlスクリプト内で use lib; する変わりになるオプション。use lib;はモジュールのパスを指定するためのperlデフォルトのモジュール。つまりここでは./lib以下のモジュール達を使いますよー、と言っている。

 まとめると 「./local以下のmoduleを使って、./lib以下のmoduleを利用したscript/sample-serverを起動しますよ!」ってこと。

 実は、というか書いていて気づいたけども、script/sample-serverの中に

use lib File::Spec->catdir(dirname(__FILE__), '../lib');

ありました。

 だから 本当は-Ilibって書かなくても起動出来る

script/sample-server

 まず全体のコードを見てみる

#!perl
use strict;
use warnings;
use utf8;
use File::Spec;
use File::Basename;
use lib File::Spec->catdir(dirname(__FILE__), '../lib');
use Plack::Builder;

use sample::Web;
use sample;
use URI::Escape;
use File::Path ();

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

    sample::Web->to_app();
};
unless (caller) {
    my $port        = 5000;
    my $host        = '127.0.0.1';
    my $max_workers = 4;

    require Getopt::Long;
    require Plack::Loader;
    my $p = Getopt::Long::Parser->new(
        config => [qw(posix_default no_ignore_case auto_help)]
    );
    $p->getoptions(
        'p|port=i'      => \$port,
        'host=s'        => \$host,
        'max-workers=i' => \$max_workers,
        'version!'      => \my $version,
        'c|config=s'    => \my $config_file,
    );
    if ($version) {
        print "sample: $sample::VERSION\n";
        exit 0;
    }
    if ($config_file) {
        my $config = do $config_file;
        Carp::croak("$config_file: $@") if $@;
        Carp::croak("$config_file: $!") unless defined $config;
        unless ( ref($config) eq 'HASH' ) {
            Carp::croak("$config_file does not return HashRef.");
        }
        no warnings 'redefine';
        no warnings 'once';
        *sample::load_config = sub { $config }
    }

    print "sample: http://${host}:${port}/\n";

    my $loader = Plack::Loader->load('Starlet',
        port        => $port,
        host        => $host,
        max_workers => $max_workers,
    );
    return $loader->run($app);
}
return $app;

 拡張子ついていないから中身を見るまで分からなかったけど、こいつは psgiだ。


 Perl Web Server Gateway Interfaceの略称。簡単に言えばWebサーバとWebアプリケーションの橋渡し的存在。


#!perl

 このプログラムはこいつで実行してくれという印みたいな感じ。シバンというらしい。


use strict;
use warnings;
use utf8;

 perlプログラムでよく見る奴ら。ほとんどのperlプログラムで使われるperl標準モジュール達。 こいつらはプログマと呼ばれる特殊モジュールで、コンパイル時にコンパイラーに指示を与える。

コード 説明
use strict; perlのルールを厳格にする。基本的に書いてれば良い。
use warnings; 詳細な警告を出してくれる。基本的に書いて(ry
use utf8; 文字コードUTF-8に統一してくれる。きほ(ry

use File::Spec;

 OSによってディレクトリのセパレータ(hoge/fuga.txtのスラッシュとか)は違っている。そういう違いを吸収してくれるモジュール。


use File::Basename;

 ファイル名からファイル名やディレクトリ名を取ってきてくれるモジュール。  例えばhoge/fuga.txtならfuga.txtがファイル名でhogeがディレクトリ名。


use lib File::Spec->catdir(dirname(__FILE__), '../lib');

 最初に書いた通り use libで利用するモジュールのpathを指定してあげる。  File::Spec->catdir();で、引数の文字列をそのOSに合ったセパレータで繋げる。  そして、 dirname()は引数のファイルのディレクトリ名を取得する。  __FILE__perlコンパイル時に展開される特殊なキーワードの一つで、このスクリプトのファイル名を表す。つまり今で言うとsample-serverになる。

 つまりこの1行は 「sample-server/../libにあるモジュール達を使いますよ」 って言ってる。

 ちなみに__FILE__のような特殊キーワードは他にもある。

キーワード 説明
__PACKAGE__ パッケージ名
__LINE__ 行番号
__DATA__ これ以降はコードとして評価されず、テキストとしてPACKAGE::DATAに関連付けられる。
__END__ これ以降はコードとして評価されず、テキストとしてmain::DATAに関連付けられる。

use Plack::Builder;

 こいつを呼べば、builderブロックの中でDSL(Domain Specific Language)を使って、Middlewareの読み込みと設定が出来る。  その中にpsgiアプリケーションのインスタンスである$appを含めてやれば、Middlewareをラップした新$appが得られる。正直ここの辺りは曖昧。


use sample::Web;
use sample;

 上でlib以下のモジュールを使えるようにしたので、早速ここで読み込んでいる。  詳しくは後で出てくるので、今はこのモジュール達を読み込んでいるんだなーと認識しておくだけ。


use URI::Escape;

 これはURIエンコード、デコードを提供するモジュールです。  でも不思議なことに、それらの操作をしていない様子。  コメントアウトしても問題ないし、読み込んでいる理由は少し分かりません。


use File::Path ();

 これは複数階層のディレクトリを作成したり削除したりする機能を提供してくれるモジュールです。  こいつも結局使ってない様子。何だろう?

 ちなみに末尾の()は、そのモジュールをimportしませんよ、ということです。


  • importって? そもそもuseって?

 そもそもの話ですが、 use hogehoge;というのは以下と(ほぼ)同義です。

BEGIN { require hogehoge; hogehoge->import; }

require hogehoge;は、そのモジュールをロードします。  そして、hogehogeモジュールはExporterモジュールを継承しているので importメソッドが使えるようになっています。importすることで、読み込んだ名前空間内に、そのモジュールのメソッド達を展開し、使えるようにします。  さらにそれらを BEGINブロックで囲むとコードのどこにあっても最初に実行されます。

 つまり、まとめると 「一番初めにモジュールをロードしてimportする」のがuseです。

 ちなみに末尾に()を付けると何故importが行われないのか?  それは、 importにリストの引数を渡すとそのリストに含まれるメソッドだけをimportすることに起因している。

 つまり、 末尾の()は空リストの()というわけ。

 何もimportしなかったら、

hogehoge->method;

と明示的にモジュール名を書いてやらないといけなくて面倒。

 話が脱線しまくりますが、じゃあなんで

...
use File::Spec;
...
use lib File::Spec->catdir(dirname(__FILE__), '../lib');
...

という風にimportしているはずのFile::Specを明示的に呼び出しているのか。

 File::Specのコードを読んでみると、Exporterを継承していませんでした。  何か理由があるのかな?


 続きます