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 で実装してしまって、リモートのサーバーステータスをグラフィカルに表示するとかアイデアは尽きません。
ほか
- http://vti.showmetheco.de/articles/2010/05/more-mojolicious-websocket-examples.html にいくと、Mojolicious + WebSocket のもっと豪華なサンプルがいくつか見られます
- Perl で WebSocket のサーバー側を処理している例はほかにないかな、と検索していたら http://cpansearch.perl.org/src/MIYAGAWA/Twiggy-0.1007/eg/chat-websocket/chat.psgi に Twiggy (AnyEvent + PSGI) ベースのサンプルが見つかりました。WebSocket を Plack/PSGI で処理できるのは嬉しいですね。
Titanium - JavaScript で iPhone/Android アプリを作る
Titanium Mobile は JavaScript で iPhone/Android のアプリ (not Webアプリ) を開発できる開発環境。詳しくは Titaniumで始めるモバイルアプリ作成の基礎知識 (1/3):Web技術でネイティブアプリを作れるTitanium(2) - @IT などに解説があります。
少し時間があったので、JavaScript で作るというのがどんな感じか試してみました。作ったアプリは
こんな感じで TableView があり、選択すると WebView でアプリ内ブラウザが立ち上がる、ブラウザはツールバーで「戻る」や「リロード」が可能。あとはタブコントロールがあったり・・・という単純なもの。初期起動画面のサイトリストは、HTTP でローカルに立てたサーバーから JSON で読み込んでいます。
Web上のドキュメントを見ながら2, 3時間試行錯誤で一応の形になりました。
Titanium での開発の実際
Titanium は開発環境ではありますが、IDE がついてくるわけではありません。今回は Emacs + js2-mode.el でゴリゴリ書きました。個人的には、IDE よりこのスタイルの方が好み。
JavaScript 中で、Titanium.UI.createWindow(...)
といった API を呼び出して iPhone/Android の UI を組み立てていきます。コードができたら、"Titanium Developer" という開発ツールでビルド。
ビルドが完了するといつもの iPhone エミュレータが立ち上がり、アプリが動きはじめます。ビルドの裏側で何が行われているかまだ把握していないのですが、立ち上がるアプリは JavaScript によるWebアプリではなく、ネイティブUIを持ったネイティブアプリです。
JavaScript で書くコードは、以下のようなコードになります。
app.js が Titanium アプリのエントリポイントになるスクリプトで、アプリを立ち上げるとまずはこのスクリプトが実行されます。
Titanium.UI.setBackgroundColor('#000'); var tabGroup = Titanium.UI.createTabGroup(); var menu = Titanium.UI.createWindow({ title :'メニュー', backgroundColor :'#fff', url : 'menu.js' }); var tab1 = Titanium.UI.createTab({ icon:'KS_nav_ui.png', title:'メニュー', window:menu }); var config = Titanium.UI.createWindow({ title : '設定', backgroundColor : '#fff', url : 'config.js' }); var tab2 = Titanium.UI.createTab({ icon:'KS_nav_views.png', title:'設定', window: config }); tabGroup.addTab(tab1); tabGroup.addTab(tab2); tabGroup.open();
Window を生成し、タブメニューを組み立てています。これで画面下部のタブメニューが表示されるようになります。各タブをクリックした後に起動するのは url プロパティで指定した menu.js や config.js です。
menu.js は、上記のスクリーンショットの GREE や Facebook などの一覧が出ているウィンドウを担当する箇所。
var win = Titanium.UI.currentWindow; var xhr = Titanium.Network.createHTTPClient(); xhr.open('GET', 'http://localhost:3000/menu'); xhr.onload = function () { var data = JSON.parse(this.responseText); var tv = Titanium.UI.createTableView({ data : data }); tv.addEventListener('click', function(e) { var row = e.rowData; var w = Ti.UI.createWindow(); var wv = Ti.UI.createWebView(); var btn_back = Ti.UI.createButton({ title : '戻る' }); btn_back.addEventListener('click', function () { wv.goBack(); }); var btn_reload = Ti.UI.createButton({ systemButton : Ti.UI.iPhone.SystemButton.REFRESH }); btn_reload.addEventListener('click', function () { wv.reload(); }); wv.url = row.url; w.toolbar = [ btn_back, btn_reload ]; w.add(wv); win.tab.open(w); }); win.add(tv); }; xhr.send();
Titanium.Network.createHTTPClient() で生成した HTTP クライアントで非同期HTTPでJSONを取得し、コールバック中でメニュー (TableView) を組み立てています。テーブルの各項目をクリックすると動的に WebView が生成されて、GREE や Facebook がその中に表示される、という具合。
今回は Titanium の組み込みの API だけを使って書いていますが、jQuery などのライブラリを読み込んで使うこともできるそう。
基本的にアプリを作っていく流れはこれだけ。JavaScript を書いては Titanium Developer でビルドしてエミュレータで確認、という感じです。Objective-C 生で書くときに比べてやはり素早く書けるし、コード量も少ない。楽で良いです。各種 API でカメラや位置情報などデバイスが提供する機能もちゃんと使えます。
間に抽象レイヤが入っている分 Objective-C/Java で作るのに比べると細かい制御ができなかったりというところはあるのかもしれません。とはいえ、十分に実用的なアプリを、書き慣れた JavaScript で手軽に(しかもクロスプラットフォームで)作れるというのはなかなかに魅力的・・・というわけで、Titanium++ です。
おまけ
JSON を返すサーバは Mojolisious::Lite で作りました。Mojolicious はこういう Quick Hack の時にも便利。
use utf8; use Mojolicious::Lite; app->renderer->default_format('json'); get '/menu' => sub { my $self = shift; my $menu = [ { title => 'GREE', hasChild => 1, url => 'http://t.gree.jp/', }, { title => 'Google', hasChild => 1, url => 'http://google.com/' }, { title => 'Facebook', hasChild => 1, url => 'http://touch.facebook.com/', }, ]; $self->stash(json => $menu); }; app->start;