【perlメモ】Mojolicious::Lite+WebSocketでのチャットプログラム
アニメの『デュラララ!!』を見たことがある人なら分かると思うんですが、主人公が開設しているサイトにチャットがあるんですが、誰かが発言するとリアルタイムに更新されるんですね。普通cgiのチャットだとこうはいかないわけでタイマーで一定時間ごとにリロードするようにしてたりしてたわけですが、それだと発言から画面に反映されるまでにタイムラグがあるんですね。デュラララ!!を見ていた技術者の人なら「高校生の個人運営のサイトなのになにこの高機能なチャット」と気になったはずです。リアルタイム更新のチャットは昔だと普通のアプリケーションとしてTCP/IPとかでソケットを制御して実現してたわけです。IRCプロトコルを使うチャットソフトなどが代表的です。あと大手各社のIM(インスタントメッセンジャー)がそうですね。
今だと比較的簡単にリアルタイム通信なウェブチャットを作れたりします。ここではWebSocketという技術を使います。ただまだ仕様策定中のためブラウザの対応はまちまちで規格統一されていないようです。とりあえずここでの動作条件は以下の通り。
- Google Chrome 14.0.835.186 m
- Mojolicious 1.97
- perl 5.14
- CentOS 5.6
WebSocketサーバーをMojolicious::Liteの機能で実現しています。常駐プログラムなので共用サーバには設置できませんけどね。そういうわけで私はVMware Player上にCentOS 5.6で構築しているサーバー環境で試しました。
基本的に下記の参考サイトのプログラムのコピペです。
Mojolicious::Lite で WebSocket を使ったチャットを作る – naoyaのはてなダイアリー
詳細は上記のサイト様をご覧下さい。大変参考になります。
ただコピペしてもつまらないのでチャット参加者の名前も指定できるように拡張してみました。誰が発言したのかわからないと悲しいですしw。また元のプログラムだと日本語が文字化けするのでその対策もしてあります。
$json->encodeするとutf8flagが落ちるようで文字化けするので前にdecode_utf8をつけてutf8フラグをonしてあげています。これで文字化けしなくなりました。
名前部分の拡張は名前とメッセージ部分をTABコードで区切って送ったものをサーバー側のプログラムでsplitしているだけです。はっきり行って手抜きです。ちゃんと作るならJavaScript側でもJSON化にして送るといいと思います。
あとMojoliciousのバージョンが上がってメソッドの名前が変わっていて元のサンプルは動かなくなっていたので新しいメソッド名に変更しています。具体的には以下の通り変更しました。
- receive_messageをon_message
- finishedをon_finish
Mojoliciousの変更点は下記のサイトで確認できます。
あと勉強がてらにテンプレートを最近のMojoliciousが吐き出すテンプレートの形式にあわせて弄ってます。
#!/usr/bin/env perl
use utf8;
use Mojolicious::Lite;
use DateTime;
use Mojo::JSON;
use Encode qw/from_to decode_utf8 encode_utf8/;
get ‘/’ => sub {
my $self = shift;
# return $self->render(j => $j, f => $f);
} => ‘index’;
my $clients = {};
websocket ‘/echo’ => sub {
my $self = shift;
app->log->debug(sprintf ‘Client connected: %s’, $self->tx);
my $id = sprintf "%s", $self->tx;
app->log->debug("id:".$id);
$clients->{$id} = $self->tx;
# $self->receive_message(
$self->on_message(
sub {
my ($self, $msg) = @_;
my ($name,$message) = split(/\t/,$msg);
unless($name){
$name = ‘名無し’;
}
my $json = Mojo::JSON->new;
my $dt = DateTime->now( time_zone => ‘Asia/Tokyo’);
for (keys %$clients) {
$clients->{$_}->send_message(
decode_utf8($json->encode({
hms => $dt->hms,
name => $name,
text => $message,
}))
);
}
}
);
# $self->finished(
$self->on_finish(
sub {
app->log->debug(‘Client disconnected’);
delete $clients->{$id};
}
);
};
app->start;
__DATA__
@@ index.html.ep
% layout ‘main’;
%= javascript begin
jQuery(function($) {
$(‘#msg’).focus();
var log = function (text) {
$(‘#log’).val( $(‘#log’).val() + text + "\n");
};
// var ws = new WebSocket(‘ws://localhost:3000/echo’);
var ws = new WebSocket(‘ws://192.168.1.5:3000/echo’);
ws.onopen = function () {
log(‘Connection opened’);
};
ws.onmessage = function (msg) {
var res = JSON.parse(msg.data);
log(‘[‘ + res.hms + ‘] (‘ + res.name + ‘) ‘ + res.text);
};
$(‘#msg’).keydown(function (e) {
if (e.keyCode == 13 && $(‘#msg’).val()) {
ws.send($(‘#name’).val() + "\t" + $(‘#msg’).val());
$(‘#msg’).val(”);
}
});
});
% end
<h1>Mojolicious + WebSocket</h1>
<p>name<input type="text" id="name" />msg<input type="text" id="msg" /></p>
<textarea id="log" readonly></textarea>
<div>
</div>
@@ layouts/main.html.ep
<html>
<head>
<meta charset="<%= app->renderer->encoding %>">
<title>WebSocket Client</title>
%= javascript ‘https://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js’
<style type="text/css">
textarea {
width: 40em;
height:10em;
}
</style>
</head>
<body><%= content %></body>
</html>
ブラウザを二つ立ち上げて一人二役でチャットしてみたんですが発言するとどちらの窓にも瞬時に発言が反映されてスゲーー!ってなりました。わびしーww。
#WebSocketとAjaxで夢がひろがりんぐwww
#このサンプルプログラム実はちょっと前に試してみて放置してました。なぜか動かなかったんですね。
最初はメソッド名が変更になっているのがわからずエラーが出るので挫折しかけたんですが検索してみると上の最近の主な変更点のページにたどり着いてメソッド名が変更になったことがわかったのでソースに反映してみたんですがサーバーに接続できるもののサーバー側から応答がないので挫折しました。
今日試してみたらなぜか動きましたw。Google Chromeが14系になったからなのかPCを再起動したからなのかは不明。
他にもMojoliciousの導入からやったので最近はCentOSもインストールすると最初からiptablesでポートを絞っているようでポートを開けてあげないと接続出来なかったりとかで一瞬「うごかねー」とか思ったり。