Mojolicious::Lite で WebSocket を使ったチャットを作る

node.jsの衝撃とWebSocketが拓く未来 (1/2):WebSocketで目指せ! リアルタイムWeb(1) - @IT という記事を読みました。node.js という V8 を用いたサーバーサイド JavaScript フレームワークを使うと簡単にイベント駆動のサーバが書ける、node-websocket-server.js を使うと node.js で WebSocket サーバが実装できる。Ajax による polling ã‚„ Long Polling などと WebSocket のアーキテクチャ比較といった内容でした。

WebSocket を使うと手軽にサーバプッシュ的なアプリケーションが作れて嬉しいのですが、現時点では、HTTPサーバー側で WebSocket を処理する下地の実装をどう用意するかというところがひとつ課題でしょう。node.js はその回答のひとつとして、なかなか面白いですね。

Perl で WebSocket サーバ ・・・ Mojolicious::Lite

さて、記事に触発されつつサーバー側を JavaScript ではなく Perl で書けたらいいなと言うことで Mojolicious::Lite を使ったサンプルを作ってみました。Mojoliciious には WebSocket を処理できるサーバー実装が同梱されていて、

use Mojolicious::Lite;

websocket '/echo' => sub {
    my $self = shift;
    $self->receive_message(sub {
        my ($self, $message) = @_;
        $self->send_message("echo: $message");
    });
};

app->start;

と書いて

% perl app.pl --daemon

でサーバーが起動、 ws://localhost:3000/echo が WebSocket のエンドポイントになり、receive_message() に渡したコールバックが、クライアント側からメッセージを受信する度にキックされるようになります。ハンドシェイクそのほかは Mojo が適当に処理してくれます。お手軽です。

サンプルの WebSocket チャットのコード

以下、サンプルで作ってみたチャットです。/ でチャット画面をHTMLで返す。/echo が WebSocket のエンドポイント。メッセージを受け取ると時刻と受け取ったテキストを、接続しているすべてのクライアントに送ります。

#!/usr/bin/env perl
use utf8;
use Mojolicious::Lite;
use DateTime;
use Mojo::JSON;

get '/' => 'index';

my $clients = {};

websocket '/echo' => sub {
    my $self = shift;

    app->log->debug(sprintf 'Client connected: %s', $self->tx);
    my $id = sprintf "%s", $self->tx;
    $clients->{$id} = $self->tx;

    $self->receive_message(
        sub {
            my ($self, $msg) = @_;

            my $json = Mojo::JSON->new;
            my $dt   = DateTime->now( time_zone => 'Asia/Tokyo');

            for (keys %$clients) {
                $clients->{$_}->send_message(
                    $json->encode({
                        hms  => $dt->hms,
                        text => $msg,
                    })
                );
            }
        }
    );

    $self->finished(
        sub {
            app->log->debug('Client disconnected');
            delete $clients->{$id};
        }
    );
};

app->start;

__DATA__
@@ index.html.ep
<html>
  <head>
    <title>WebSocket Client</title>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" ></script>
    <script type="text/javascript" src="/js/ws.js"></script>
    <style type="text/css">
      textarea {
          width: 40em;
          height:10em;
      }
    </style>
  </head>
<body>

<h1>Mojolicious + WebSocket</h1>

<p><input type="text" id="msg" /></p>
<textarea id="log" readonly></textarea>

</body>
</html>

クライアントの js の中身。WebSocket API で /echo に接続して入出力を適当に処理。

$(function () {
  $('#msg').focus();

  var log = function (text) {
    $('#log').val( $('#log').val() + text + "\n");
  };
  
  var ws = new WebSocket('ws://localhost:3000/echo');
  ws.onopen = function () {
    log('Connection opened');
  };
  
  ws.onmessage = function (msg) {
    var res = JSON.parse(msg.data);
    log('[' + res.hms + '] ' + res.text); 
  };

  $('#msg').keydown(function (e) {
    if (e.keyCode == 13 && $('#msg').val()) {
        ws.send($('#msg').val());
        $('#msg').val('');
    }
  });
});

Google Chrome で localhost:3000 にアクセス、ちゃんと動きました。

こうして WebSocket のサーバー側を Perl で実装できる、とわかれば後は既存の資産を生かしていろんなことが考えられますね。先の記事の Activity Monitor のようなものを、サーバーで Perl で実装してしまって、リモートのサーバーステータスをグラフィカルに表示するとかアイデアは尽きません。

ほか