自分用めも

初心者ちっくなプログラムネタを中心に、自分用の覚え書きをメモっていくための場所です。

NginxでPSGI/Plack(Starlet)でHello world

前回記事の環境を変えて、PSGIが動くようにします。

PSGI/Plackとは

mod_perlFastCGIと同じく、モジュールをオンメモリで永続化することによって処理の高速化を図る仕組み。サーバ(デーモン)です。
似たコンセプトの仕組みがいくつかある現状、特にフレームワーク側でそれぞれ別の接続対応をする必要があり、コネクタ部分の無駄な開発が繰り返されていました。
そこでマルチコネクタ的なものを作って、インターフェイス部分を吸収しようということで生まれたのがPSGIという規格らしいです。
第1回 PSGI/Plack―フレームワークとサーバをつなぐエンジン (1):Perl Hackers Hub|gihyo.jp … 技術評論社

PSGIを使えば上記のmod_perlFastCGIなどを独自設定を気にせず使えるようになるのですが、せっかくだからPSGIだけの環境で実行できる、perlネイティブのサーバがあるといいよね、ということで生まれたのがPlackとのこと。
Plackはあくまでリファレンス(参考)実装として作られたらしいのですが、付属のツール類はデファクトスタンダードとなっており、本体も出来の良さからテストサーバなどでは現在でも普通に使われているらしい。

Nginxでは静的なファイルの配信しか出来ないと前回書きましたが、動的なページを配信するためにこのようなデーモンにリクエストを引き継ぎ、結果を受け取ってクライアントに返す橋渡しのような処理を行います。これをリバースプロキシというらしい。

Starletとは

PSGIの規格に則って作られたサーバ。
高速で動くこと、ホットデプロイが出来ること、プロセスごとの負荷分散の仕組みがあることなどから人気となっています。

ここから本題

やることとしては、

  1. NginxのPSGI向け設定
  2. Plack、Starletのインストー
  3. 最低限のコーディング

となります。

1.NginxのPSGI向け設定

PSGI(Starlet)とNginxを繋ぐ方法は、TCP接続とUNIXドメインソケット接続の2通りあります。
調べなきゃ寝れない!と調べたら余計に寝れなくなったソケットの話 - Qiita
例外もあるらしいのですが(Solaris)、UNIXドメインソケット通信のほうが圧倒的に早い。
ということでこちらで繋ぐようにします。

Nginx側でいじるのは二箇所だけです。
前回記事の「/srv/www/nginx.conf」。バーチャルホストの設定部分。

vim /srv/www/nginx.conf
upstream 192.168.0.10 { #(b)
	server unix:/tmp/192.168.0.10.sock;
}

server {
	listen      80 default_server;
	server_name 192.168.0.10;
	access_log  /srv/www/192.168.0.10/logs/access.log main;
	error_log   /srv/www/192.168.0.10/logs/error.log warn;
	etag off;

	location / {
		proxy_set_header Host $host; #(a)
		proxy_pass http://192.168.0.10;
	}
}
(a) proxy_set_header、proxy_pass

ソケット用の設定です。
Starlet + Server::Stater で UNIX domain socketに対応しました - Hateburo: kazeburo hatenablog

(b) upstream proxy_pass

UNIXドメインソケット接続ではファイルを使うのですが、そのpathを指定します。
後ほど設定する、接続側のStarletからも同じpathを指定します。

2.Plack、Starletのインストー

CPANの設定で「依存関係のあるモジュールを全てインストールする」としていれば、

cpan install Starlet

これでPlack(ツール類含む)からStarletからまで全てインストールされます。
この上なくかんたんです。

また、必須ではないのですが下記をインストールします。

cpan install Linux::Inotify2 #(a)
cpan install HTTP::Parser::XS #(b)
cpan install Router::Simple #(c)
(a) Linuxファイルシステムの監視

ファイルが更新された時に自動でStarletの再起動を行うオプションがあるのですが、このモジュールがあると速度が向上します。

(b) Starletのパフォーマンス向上

あるとパフォーマンスが上がるらしい。
必須ではないので依存関係がなく、自動ではインストールされない。
第24回 PSGI/Plack実践入門―Starman,Starlet,Twiggy,Plack::Middleware,Server::Starter(2):Perl Hackers Hub|gihyo.jp … 技術評論社

(c) ルータ、ディスパッチャー

普通の?apachecgiだとURLによって処理するCGIが変わりますが、PSGIの場合常駐スクリプト(daemon)が一人で窓口を担当することになります。
リクエストURLと対応するモジュールの紐付けが必要です。
フレームワークを使わない前提だと、該当機能を担う、いわゆるディスパッチャーがありません。

手法は幾つかあり、PSGI提唱者のmiyagawaさんがサンプルを公開されています。
GitHub - miyagawa/plack-dispatching-samples: Examples of Plack dispatcher using various CPAN modules

今回はその中からRouter::Simpleを使ってみました。

3.最低限のコーディング

前回と同様、ドキュメントルートは/srv/www/192.168.0.10/htmlの想定です。
Nginxにおけるドキュメントルートの設定は、すなわち静的なファイルの場所ということなのであんまり関係ないのですが、スクリプトを/srv/www/192.168.0.10/の下に配置しています。

Starletサーバの起動スクリプトを書く

コマンドラインで叩いてサーバ(daemon)を起動させるわけですが、コマンドの一部をシェルスクリプトに書き出しています。
ホットデプロイ時にサーバ自体の設定を変えたい場合に、この書き方をしていると可能になるらしいです。
PSGI/Plackアプリケーションの起動方法いろいろと本番環境アレコレ - blog.nomadscafe.jp

vim /srv/www/192.168.0.10/scripts/run.sh
#!/bin/bash
exec plackup -s Starlet \
  --spawn-interval 0.1 --max-workers 10 \
  --max-reqs-per-child 5000 --min-reqs-per-child 4000 \
  -a /srv/www/192.168.0.10/scripts/index.psgi
PSGIのフロントとなるスクリプトを書く

ここで追加インストールしたRouter::Simpleを使っています。
ちゃんと作る場合、ルーティングの設定は切り出したほうがいいでしょう。

vim /srv/www/192.168.0.10/scripts/index.psgi
#!/usr/bin/perl

BEGIN {
	push @INC, ('/srv/www/192.168.0.10/lib/');
}

use strict;
use warnings;
use Plack::Request;
use Plack::Response;
use Router::Simple;

use MyTest;

my $router = Router::Simple->new();
$router->connect('/', { controller => 'MyTest', action => 'index' }, { method => 'GET' });

return sub {
	my $env = shift;
	my $p   = $router->match($env) || return Plack::Response->new(404)->finalize;

	my $req = Plack::Request->new($env);
	my $controller = $p->{controller};
	my $action = lc($req->method) . '_' . $p->{action};
	my $res = $controller->can($action)->($req, $p);
	$res->finalize;
};

この例では「/」にアクセスされた場合に、MyTestモジュールのget_indexというメソッドを呼び出す設定をしています。
Router::Simpleのmethodはオプション引数なので、そこまではいらないって場合は取ってしまってもいいかも。
取ろうと思えば$req->methodでとれますしね。

Hello world用モジュール
vim /srv/www/192.168.0.10/lib/MyTest.pm
package MyTest;

use utf8;
use Encode;

sub get_index {
	my ($req, $p) = @_;
	my $res = Plack::Response->new();

	my $html = "Hello world.";

	$res->status(200);
	$res->headers({'Content-Type' => 'text/html'});
	$res->body(
		encode_utf8($html)
		)
	);

	return $res;
}

ここまででモジュールの用意は完了です。

Starletの起動

起動用モジュールを絡めて起動します。

start_server --signal-on-hup=USR1 --path /tmp/192.168.0.10.sock -- /srv/www/192.168.0.10/scripts/run.sh

これで192.168.0.10にアクセスすると、Hello world.が表示されます。