OpenShift OnlineでPSGIアプリ立ててWebSocket挿す
こんにちは、 @debug-ito です。PlackとAnyEventが好きです。よろしくおねがいします。
今回は最近作ったものの宣伝も兼ねて、OpenShift Onlineのチュートリアル的な記事を書いてみようと思います。
OpenShift Onlineって?
Red Hatが提供しているパブリックPaaSです。Google App EngineやHerokuなんかと同じような感じで、Webアプリケーションとかをホストすることができます。小規模なアプリなら無料で使えます。
OpenShift Onlineでは公式でPerlがサポートされています。使い方は @mercysluck さんの記事が参考になります。
ですが、OpenShiftが公式でサポートしているPerlパッケージはApache httpd + mod_perlな構成になっていて、自分の好きなPSGIサーバを動かせんのです。PlackとAnyEventが大好きな僕としてはなんとしてもTwiggyを動かしてやりたいわけなのです。
なにを作るのか
というわけで、今回は次のようなWebアプリをOpenShift Onlineで作ってみます。
- 簡単なWebチャットアプリ
- チャットルームはひとつ
- Plackベース
- PSGIサーバはTwiggy
- Webアプリケーションフレームワークは使わない
- チャットメッセージはWebSocketで配信
なお、完成品を http://chattest-debugito.rhcloud.com/ で動かしています。
完成品のソースコードは https://github.com/debug-ito/example-openshift-chat です。
開発環境構築
まずはOpenShift Onlineにユーザ登録し、必要なツールをインストールして開発環境を整えます。
-
https://www.openshift.com/ から、"ONLINE"の"SIGN UP FOR FREE"をクリックしてユーザ登録。
-
https://www.openshift.com/developers/rhc-client-tools-install を参考に、
rhc
ツールをインストールします。- Webベースの管理画面からでも結構いじれますが、
rhc
があった方がまあ何かと便利です。
- Webベースの管理画面からでも結構いじれますが、
-
初期設定をします。
$ rhc setup
-
初期設定ではSSH公開鍵を登録したり、自分のドメイン名(名前空間とも言う)を登録したりします。SSH公開鍵はあとでgitアクセスする時に必要になります。
rhc
コマンドやWebコンソールから後で登録することもできます。
ここまでくればアプリ開発の準備完了です。
アプリケーションの雛形を作る
アプリケーションを作るには、rhc app create
コマンドを使います。
$ rhc app create -a chattest -t https://raw.github.com/debug-ito/openshift-cartridge-plack/release_0.1.0/metadata/manifest.yml
これを実行すると すっごい待たされますが、辛抱強くお待ちください。 ほんとすいません。
rhc app create
コマンドの-a
オプションはアプリ名(今回はchattest
とする)、-t
オプションはアプリケーション種別です。OpenShift用語では、「Webカートリッジ(Web Cartridge)」などと言います。
通常、Perlアプリを作る場合は-t perl-5.10
としますが、前述の通り、OpenShift公式のperl
カートリッジはPSGIサーバを選べないので、拙作のPlackカートリッジを使うように指定しています。
- openshift-cartridge-plack : https://github.com/debug-ito/openshift-cartridge-plack
Plackカートリッジではアプリ作成時に毎回cpanmやPlackなどのモジュールをインストールするので時間がかかります。いっそのことバンドルしてしまった方がいいかもしれませんね。
Plackカートリッジアプリの雛形
app create
がうまくいけばchattest
ディレクトリが作られているはずです。このディレクトリはローカルgitレポジトリになっています。
$ cd chattest
$ ls -a
. .. .git .proverc README.md app.psgi cpanfile lib plack_config.pl t
さらに、アプリケーションはもう稼動しているはずです。http://chattest-YOUR_DOMAIN.rhcloud.com (YOUR_DOMAINは登録したドメイン名)にアクセスすると、"It works!"と言われます。
雛形の構成はこんな感じです。PSGIアプリ開発ではおなじみのものではないでしょうか。
- README.md : このカートリッジの使い方を書いています。
- app.psgi : アプリケーション本体のスクリプトです。PSGIアプリオブジェクトを返します。
- cpanfile : アプリが依存するCPANモジュールを記述します。記法はcpanfile形式です。
- .proverc : テストを実行する
prove
コマンドの実行オプションを指定します。 - plack_config.pl :
plackup
コマンドの設定を記述します。 - lib/ : アプリで使う自作モジュールを格納します。このディレクトリは自動的に
PERL5LIB
パスに追加されます。 - t/ : テスト用スクリプトを格納します。
この中であまり馴染みがないのはplack_config.plでしょうか。この内容はこうなっています(コメントなどは除く)
$ENV{PLACK_ENV} = "development";
## $ENV{PLACK_SERVER} = "Starman";
要は、plackup
コマンドの設定を環境変数経由で行うためのスクリプトです。
今のところ、PlackカートリッジではDaemon::Controlで作ったスクリプトからplackup
コマンドを実行してアプリを立ち上げるようにしています。その際、plack_config.plスクリプトをロードしてplackup
の設定をしています。
アプリを開発する
それでは、ローカルにクローンしたchattest
ディレクトリでアプリを開発します。完成品は https://github.com/debug-ito/example-openshift-chat に置いています。
ポイントになるところだけ抜粋します。まずはplack_config.plです。
$ENV{PLACK_ENV} = "deployment";
$ENV{PLACK_SERVER} = "Twiggy";
PSGIサーバとしてTwiggyを指定します。
次はcpanfileです。今のところPlackカートリッジには依存モジュールをスキャンする機能はないので、依存モジュールは全てここに書くようにします。
requires "ExtUtils::ParseXS", "3.22";
requires "Twiggy";
requires "EV";
requires "Log::Dispatch::FileWriteRotate";
requires "Time::Piece";
requires "Data::Section::Simple";
requires "Plack::App::WebSocket";
requires "Text::Xslate";
なお、ExtUtils::ParseXSを明示的に書かないとText::Xslateのインストールでコケます(リトライすればOK)。これは、バージョンの異なるExtUtils::ParseXSモジュールが複数のパスにインストールされるという、Plackカートリッジの少々特殊な環境が原因のようです。
以下では、app.psgiの内容を抜粋しながら順に解説していきます。
WebSocketエンドポイントを作る
my %websockets = ();
sub report_number_of_peole {
my $socket_num = keys %websockets;
foreach my $conn (values %websockets) {
$conn->send("---- There are $socket_num people in this chat now.")
}
}
my $websocket_endpoint = Plack::App::WebSocket->new(
on_establish => sub {
my ($conn) = @_;
$websockets{"$conn"} = $conn;
$conn->on(message => sub {
my ($conn, $message) = @_;
my $cur_time = current_time_str();
$message = "[$cur_time] $message";
foreach my $conn (values %websockets) {
$conn->send($message);
}
});
$conn->on(finish => sub {
my ($conn) = @_;
delete $websockets{"$conn"};
report_number_of_peole();
});
report_number_of_peole();
}
);
PSGIアプリでWebSocketコネクションを受け付けるために、拙作のPlack::App::WebSocketを使います。こいつは単体でPSGIアプリとなるので、後でmountするもよし、アプリ内でcallするもよし、です。
このアプリでは、受け付けたWebSocketコネクションを%websockets
内にためておいて、受信したメッセージを単純にブロードキャストします。また、コネクションの出入りがあるたびに現在のコネクション数をブロードキャストします。
ビューとなるHTMLページを作る
my $template = Text::Xslate->new(
path => [{index => get_data_section("index.html")}],
cache_dir => "$ENV{OPENSHIFT_TMP_DIR}/xslate",
);
my $page_html = $template->render(
"index", { app_fqdn => $ENV{OPENSHIFT_APP_DNS} }
);
Text::XslateとData::Section::Simpleを組み合わせています。
OpenShiftでは、アプリのために様々な情報を環境変数として提供しています。ここでは、
-
OPENSHIFT_TMP_DIR
: 一時ファイルを格納するディレクトリ -
OPENSHIFT_APP_DNS
: このアプリのFQDN
を利用しています。FQDNは後でWebSocketコネクションを張る時に必要となります。
利用できる環境変数のリストは http://openshift.github.io/documentation/oo_user_guide.html#environment-variables を参照してください。
アプリ本体を作る
my $app = sub {
my $env = shift;
if($env->{PATH_INFO} eq "/websocket") {
return $websocket_endpoint->call($env);
}else {
return [200,
["Content-Type" => "text/html; charset=UTF-8",
"Content-Length" => length($page_html)],
[$page_html]];
}
};
単純に、リクエストパスに応じてWebSocketかHTMLレスポンスかを振り分けているだけです。
loggerをセットして終了
my $logger = Log::Dispatch::FileWriteRotate->new(
min_level => "info",
dir => $ENV{OPENSHIFT_PLACK_LOG_DIR},
prefix => "access.log",
size => 10*1024*1024,
histories => 5
);
builder {
enable "AccessLog", logger => sub {
$logger->log(level => "info", message => $_[0]);
};
$app;
};
ログのローテーションなどはアプリ側で面倒見る必要があるので、今回はLog::Dispatch::FileWriteRotateを使ってみました。
Plackカートリッジのログ出力用ディレクトリはOPENSHIFT_PLACK_LOG_DIR
環境変数で取得できます。ここに吐かれたログは
$ rhc tail chattest
としてリアルタイムに監視することができます。
WebSocketを接続する
var websocket_url = "ws://<: $app_fqdn :>:8000/websocket";
(中略)
var ws = new WebSocket(websocket_url);
クライアントJavaScriptのうち、WebSocket接続を行う部分がここです。
<: $app_fqdn :>
はText::XslateによってアプリのFQDNに置換されます。
なお、OpenShiftではWebSocketコネクションの接続先ポートが8000番(SSLを使う場合は8443番)である点に注意が必要です。
アプリをデプロイする
アプリをデプロイするには、変更をローカルレポジトリにコミットしたあと、それをOpenShiftへプッシュすればOKです。
$ git add app.psgi
$ git add cpanfile
...
$ git commit -m "implement WebSocket chat"
$ git push
すると、Plackカートリッジが必要なCPANモジュールをインストールし、テストを実行してから改めてアプリを立ち上げます。
なお、今のところPlackカートリッジはホットデプロイに対応していません。ビルド中はアプリケーションが停止するので注意してください。
まとめ
OpenShift Onlineを使ってPlackベースのWebアプリケーション開発をしてみました。今回はあえてWebアプリケーションフレームワークを使いませんでしたが、PSGI互換のものであればどれでも自由に使うことができるでしょう。
OpenShift OnlineではWebアプリケーションの他、各種データベースサーバやJenkinsサーバを立てたり、HAProxyを使ってスケールアウトするアプリを立てたりすることができます。ただ、あまり凝った構成にしようとすると"gear"(リソースの単位)が足りなくなるので有料プランを使う必要があります。
個人的には無料の範囲でもとりあえずWebSocketの挿せるアプリを立ち上げられるので、ちょっと遊ぶには都合がいいと思ってます。
次回は
以前ちょっとハマったネタを思い出したのでまた書かせていただきます。