PSGIミドルウェア
PSGIミドルウェアとは
PSGIアプリケーションとPSGIサーバの間にあり、Plack::Middleware
という名前空間のもとに実装され、
Plack::Builder
モジュールを利用すると、
use Plack::Builder;
my $app = sub {…};
$app = builder {
enable 'Plack::Middleware::Lint';
enable 'Plack::Middleware::StackTrace';
$app;
};
Plack::Builderが提供するbuilderブロックの中で、
以降ではプロダクション環境で使用されるPlack::Middlewareをいくつか紹介します。
Static ── 静的コンテンツの配信
Plack::Middleware::StaticはCSSやJavaScriptなどの静的コンテンツの配信を行うミドルウェアです。Plackのディストリビューションに含まれています。
builder {
enable 'Static',
path => qr!^/(css|js|img)/!,
root => '/path/to/public';
$app;
}
上記のような設定を行うと、
ただし、
AccessLog ── アクセスログの表示
AccessLogはその名のとおりアクセスログを出力するためのミドルウェアです。
builder {
enable "AccessLog",
format => "combined";
$app;
};
ログのフォーマットにはcombined
、common
、
AccessLogミドルウェアでApacheでサポートされる、%D
enable "AccessLog::Timed",
format => '%h %l %u %t "%r" %>s %b %D';
ログの出力先はデフォルトでは$env->{psgi.
が示すファイルハンドルになります。多くの場合は標準エラーです。ファイルに書き出すときはloggerオプションにコードリファレンスを渡します。
use File::RotateLogs;
my $logger = File::RotateLogs->new();
builder {
enable "AccessLog",
format => 'combined',
logger => sub { $logger->print(@_) }
$app;
}
File::RotateLogsはログファイルを指定した時間ごとに分割し、
ReverseProxy── アクセス元IPアドレスの取得
PSGIアプリケーションをApacheやnginxなどのリバースプロキシのもとで動作させた場合、$env->{REMOTE_
はリバースプロキシのIPアドレスとなります。同じホスト上にリバースプロキシがあれば127.
が格納されます。本来のクライアントのIPアドレスはどうなるかというと、X-Forwarded-For
というヘッダの末尾に追加されて送られてきます。X-Forwarded-For
ヘッダはRFCなどで定義されているヘッダではありませんが、
PSGIアプリケーションでは次のようなコードで、X-Forwarded-For
から取得できます。
my $xff = $env->{HTTP_X_FORWARDED_FOR};
my ($ip) = $xff =~ /([^,\s]+)$/;
Plack::Middleware::ReverseProxyはX-Forwarded-For
からIPアドレスを取得し、$env->{REMOTE_
の上書きのほか、
use Plack::Builder;
builder {
enable_if { $_[0]->{REMOTE\_ADDR} eq '127.0.0.1' }
'ReverseProxy';
$app;
};
X-Forwarded-For
ヘッダはクライアント側で簡単に詐称できてしまうので、X-Forwarded-For
を信用するようにしてください。
ServerStatus::Lite── サーバ状態の可視化
Starman、
ServerStatus::Liteにはいくつかオプションがあります。
builder {
enable "Lite",
path => '/server-status',
allow => ['127.0.0.1','192.168.0.0/16'],
counter_file => '/path/to/counter_file',
scoreboard => '/path/to/scoreboard';
$app;
};
path
にサーバの状態を表示するためのURIを指定し、allow
にそのURIに対してアクセス許可されるIPアドレスを指定します。もしIPアドレスが指定されていない場合、counter_
はアクセス数と総転送量を記録するためのファイルです。そしてscoreboard
にワーカプロセスの状態を記録するためのディレクトリを指定します。
HTTPクライアントでpath
に指定したURIにアクセスすると、
$ curl http://localhost:5000/server-status Uptime: 1381942535 (23 seconds) Total Accesses: 3 Total Kbytes: 0 BusyWorkers: 1 IdleWorkers: 9 -- pid status remote_addr host method uri protocol ss 80060 _ 127.0.0.1 localhost:5000 GET / HTTP/1.1 14 80061 _ 127.0.0.1 localhost:5000 GET / HTTP/1.1 13 80062 _ 127.0.0.1 localhost:5000 GET / HTTP/1.1 13 80063 A 127.0.0.1 localhost:5000 GET /server-status HTTP/1.1 0 80064 .
図5は、

PSGIアプリケーションのホットデプロイ
Server::Starterを使ったホットデプロイ
Server::Starterは、
Server::Starter経由でPSGIサーバを起動するにはstart_
コマンドを使います。
$ start_server --port 5000 -- plackup \
-s Starlet -a hello_world.psgi
上記のコマンドを実行すると、exec(2)
します。このときに、
子プロセスのStarletは環境変数からファイルディスクリプタを読み出し、
アプリケーションへの機能追加などでPSGIサーバの再起動が必要になった際には、
$ kill -HUP {pid}
Server::StarterはHUPシグナルを受け取ると、
Server::Starterはこのように再起動処理を行うことで、
現場で使われるPSGIアプリケーションの起動方法
Server::StarterとPSGI/start_
を次のようにして起動します。
$ start_server --port 5000 \
--signal-on-hup=USR1 \
-- /path/to/run_app.sh
ポイントは2つあります。1つ目はstart_
コマンドにplackup
コマンドを直接渡さずにシェルスクリプトを使っている点、start_
の--signalon-hup
オプションとStarletの--spawn-interval
オプションです。
Webサービスの運用中にServer::Starter経由で起動しているStarletのワーカ数を変更をしたいと思っても、--maxworkers
が書かれていると、start_
を起動すると、
#!/bin/bash
exec plackup -s Starlet \
--spawn-interval 0.1 --max-worker 30 \
--max-reqs-per-child 1000 --min-reqs-per-child 500 \
-a /path/to/app.psgi
大量のアクセスを受けているサービスでは、fork(2)
による負荷が問題となるケースがあります。Starletの起動オプションに--spawn-interval=秒数
を追加すると、fork(2)
する際に指定した秒数だけ間隔を開けます。また、--spawn-interval
の秒数を空けてワーカプロセスを順次終了させます。
start_
に追加した--signal-on-hup=USR1
はこのStarletの機能を活用するためのオプションで、
まとめ
本稿では、
さて、