あけましておめでとうございます。 お正月ですがこのブログはふつーに進行します。

GAE 1.4 で追加された Channel API を使って簡単なチャット のサンプルを WSJS で書いてみた。

参考にしたのはオフシャルのページ。
http://code.google.com/intl/en/appengine/docs/java/channel/

使い方は以外に簡単。

クライアント側:

  1. 一意のClientIDを送ってサーバからトークンを貰う。
  2. 専用APIにトークンを渡してサーバに接続する。
    • goog.appengine.Channel
  3. メッセージ受信関数を専用APIに設定する。

サーバ側:

  1. ClientIDを受け取ってトークンを返す。 同時にClientIDを保存する。
    • ChannelService.createChannel()
  2. 何らかのイベントでクライアントにメッセージを送信する。 このとき 1. で保存した ClientID が必要。
    • ChannelService.sendMessage()
唯一面倒なのはサーバ側での ClientID の保存。 トークンは2時間しか有効では無いので Bigtable ではなく Memcahe を使うのが妥当と思われる。

サンプルコード。
index.html:

<html> <head> <script src="/lib/jquery/jquery-1.4.4.js"></script> <script src="/lib/json2.js"></script> <script src="ChannelServer.rpjs"></script> </head> <body> <!-- body の中に --> <script src='/_ah/channel/jsapi'>/* */</script> <script> var channel = null; var socket = null; function onMessage(msg) { $("#view").val($("#view").val() + msg.data+"\n"); } function onOpened() { alert("接続しました。"); $("#sec-main").show(); $("#sec-connect").hide(); } function onClosed() { alert("切断しました。"); $("#sec-main").hide(); $("#sec-connect").show(); } function connect() { var clientId = $("#clientId").val(); var channelToken = ChannelServer.getToken(clientId); channel = new goog.appengine.Channel(channelToken); socket = channel.open(); socket.onmessage = onMessage; socket.onopen = onOpened; socket.onclose = onClosed; } function disconnect() { socket.close(); } function send() { ChannelServer.sendMessageAll($("#clientId").val()+":"+$("#message").val()); } </script> <div id="sec-connect"> ClientId:<input id="clientId" /><button type="button" onclick="connect()">接続</button> </div> <div id="sec-main" style="display:none;"> <button type="button" onclick="disconnect()">切断</button> <hr/> <textarea id="view" cols="40" rows="10"></textarea> <br/> Message:<input id="message" /><button type="button" onclick="send()">送信</button> </div> </body> </html>

ChannelServer.rpjs:(サーバ側)

// import. var MemcacheServiceFactory = Packages.com.google.appengine.api.memcache.MemcacheServiceFactory; var ChannelServiceFactory = Packages.com.google.appengine.api.channel.ChannelServiceFactory; var ChannelMessage = Packages.com.google.appengine.api.channel.ChannelMessage; var CACHE_NS = "channel-test"; var ChannelServer = { // クライアントIDから接続トークンを得る。 getToken : function(clientId) { var clients = ChannelServer.getClients(); clients.add(clientId); ChannelServer.saveClients(clients); var channelService = ChannelServiceFactory.getChannelService(); var token = channelService.createChannel(clientId); return token; }, // 全員にメッセージを送る sendMessageAll : function(msg) { var channelService = ChannelServiceFactory.getChannelService(); var clients = ChannelServer.getClients(); var ite = clients.iterator(); while (ite.hasNext()) { var clientId = ite.next(); channelService.sendMessage(new ChannelMessage(clientId, msg)); } }, // クライアントの一覧をキャッシュに持つ。 getClients: function () { var memcache = MemcacheServiceFactory.getMemcacheService(CACHE_NS); var clients = memcache.get("clients"); if (clients == null) { clients = new java.util.HashSet(); } return clients; }, saveClients : function(clients) { var memcache = MemcacheServiceFactory.getMemcacheService(CACHE_NS); memcache.put("clients", clients); } } exports(ChannelServer);

実行してみる。 ChromeとFierfox で2つ開く。

ClientID を其々 taro と hanako として接続する。

hanako が「こんにちは」と入力するとクライアントのアクション無し に taro 側にもメッセージが表示される。

taro 側で入力しても同じ。

これで一応チャットアプリっぽい動作をしている事になる。

疑問点:
socket.close() を呼んでも onclose が発生しない。 サーバ側にも明示的に接続を切るメソッドが無い。

はまり所:
一つだけはまった。
<script src='/_ah/channel/jsapi'> を <head>の中で呼ぶと JavaScript エラーになる。<body>の中で呼ぶ必要が有る。

Chrome:
    Uncaught TypeError: Cannot call method 'appendChild' of null

Firefox:
    b is null

サンプルを動かす場合は こちら からどうぞ。