WebSocket を使って Amon2 でチャットプログラムを書いてみる
ずいぶん前ですが Amon2 が WebSocket に対応したということで、今更感のあるチャットプログラムを書いてみることに。
今更とは言いつつも社内では IRC を使うことができないので、ちょっとしたメッセージがやりとりできるものを作ってみようかなと思い、そのベースとなるようなイメージで作ってみました。
オマケで以前から使ってみたいなぁと思っていた Redis や Proclet なんかも (無駄に) 使ってみました。
適当な Heartbeat で WebSocket を維持していたり、エラー処理が適当すぎて Member の一覧が嘘つきだったり *1 と、まだまだイケてない部分ばかりですが、最近時間がなかなか取れないこともあり、とりあえず動くし晒してみようかと思います。
Twincle の名前は昔テストで Twitter Client を作ったときに押さえた ID をそのまま踏襲しただけで、特に意味はないです。
README すらないご覧のザマだよ。
- hatyuki / twincle
- https://github.com/hatyuki/twincle
セッションで Socket を管理しているため、同一ブラウザで2つウィンドウ立ち上げてもうまく動きません。この辺は Hash::MultiValue をうまく使えばいいのかなー。などと検討中。
なんとなく工夫した気がするところ
WebSocket ではバイナリデータを送受信することができるので、今回は Perl (Amon2) ←→ JavaScript 間を MessagePack でバイナリ化してやりとりをしてみました。
Amon2 では下位のレイヤーでは Protocol::WebSocket を利用して WebSocket のやりとりをしていますが、その部分は見事に隠蔽されており、プログラムするときは Protocol::WebSocket の存在を意識せずに、文字列を単に渡すだけで WebSocket を扱うことができます。
- MyApp::Web::Dispatcher
get '/socket' => sub { my $c = shift; $c->websocket( sub { my $ws = shift; $ws->send_message("文字列を渡すだけで OK"); # ... } ); };
が、バイナリデータを送受信するためには Protocol::WebSocket::Frame のコンストラクタで送信したいフレームがバイナリデータであることを指定する必要があるため、ちょっと都合が悪いこととなります。
そこで、Amon2::Plugin::Web::WebSocket をちょっとだけ編集して、文字列だけではなく Protocol::WebSocket::Frame そのものも渡せるようにしました。
- MyApp::Plugin::Web::WebSocket
# ... (中略) $ws->{send_message} = sub { my $message = shift; # Protocol::WebSocket::Frame オブジェクトを受け付けるように unless (eval { $message->isa('Protocol::WebSocket::Frame') }) { $message = Protocol::WebSocket::Frame->new($message); } $h->push_write($message->to_bytes); }; # ... (中略)
これにより以下のように、バイナリフレームを送信することができるようになります。
- MyApp::Web::Dispatcher
get '/socket' => sub { my $c = shift; $c->websocket( sub { my $ws = shift; # バイナリフレームを作成して my $frame = Protocol::WebSocket::Frame->new( type => 'binary', buffer => $binary_strings, ); # 送信 $ws->send_message($frame); # ... } ); };
無駄にハマって解決できていないところ
手元の Mac では再現しませんが、Debian 上で実行するとなぜか LWP::UserAgent がすごい時間がかかる。
具体的には不自然に必ず30秒以上かかる。なんかタイムアウトを待っているみたいな。。。
そのせいで Net::Twitter::Lite を使った Twitter の認証に必要以上の時間がかかってしまい、困ったことになりました。
とりあえずの対処として、Net::Twitter::Lite が LWP::UserAgent ではなく Furl を使うように無理矢理パッチをあてました。
package Twincle::Auth::Site::Twitter; use strict; use warnings; use Furl; use Net::Twitter::Lite; sub import { no warnings 'redefine'; # "default_header" メソッドがないとエラーになるので申し訳程度に追加 *Furl::default_header = sub { }; my $orig = *Net::Twitter::Lite::new{CODE}; *Net::Twitter::Lite::new = sub { my ($class, %args) = @_; # UserAgent を LWP::UserAgent から Furl に差し替え $args{ua} = Furl->new; $args{legacy_lists_api} = 0; $orig->($class, %args); }; } 1;
LWP::UserAgent が遅い原因については据え置き中。
時間があるときに調べてみよっと。
まとめ
Amon2 の WebSocket サポートを使うと、すごく簡単に WebSocket を使うアプリケーションが作成できました。
バイナリデータを送受信するために改良した部分は、もう少し確認して本家の方へ pull request してみたいなぁと思います。
まだまだ追加したい機能はたくさんありますが WebSocket や Redis など、使ってみたかったものを使いつつ動くということで
わーい。
*1:きちんとログアウトしないと幽霊部員になる