Lift フレームワークを使って Comet をためす(1)

Lift の練習として、Comet を使用してみることにした。最終的には Comet によるチャットが作れればいいかな・・・(希望)
とりあえずは、表示されている時間を Comet を使用して1秒毎に更新してみる。

情報源は id:yuroyoro さんのエントリと、TheLiftBook.pdf*1(以後、PDFと記す)を使用。
Comet によるチャットは Lift の wiki にもあるのだけど、そこはスルーの方向で。

雛形作成

お約束だけど、mvn の archtype:generate で雛形作成。
Windows の場合、\ で次行に入力を続けることが出来ないので、\ を除去して一行で入力。


>mvn archetype:generate -U \
-DarchetypeGroupId=net.liftweb \
-DarchetypeArtifactId=lift-archetype-blank \
-DarchetypeVersion=1.0.2 \
-DremoteRepositories=http://scala-tools.org/repo-releases \
-DgroupId=sandbox.comet \
-DartifactId=comet-chat \
-Dversion=1.0-SNAPSHOT

これで、comet-chat というディレクトリ下に、ずらずらっと雛形が作成される。mvn jetty:runで試しに実行できる。

とりあえず Comet を試す

フォームの扱いとかモデルとか全部すっとばして、PDF の 9.5 章あたりを参考に、とりあえず Comet を試してみる。

index.html に comet タグを追記

index.html に以下を追記する。lift:comet はページに CometActor を配置するタグ。type でクラス名を指定する。
このクラスは Boot クラス(src/main/scala/bootstrap/liftweb/Boot.scala)で指定したパッケージ配下の comet パッケージから検索される・・・のだと思う。

<lift:comet type="Messages">
  Hello COMET!! - <msg:time>Missing time.</msg:time>
</lift:comet>
Messages Comet アクターを追加

Messages クラス(src/main/scala/sandbox/comet/comet/Messages.scala)を追加する。<lift:comet type="Messages"/> と対応するため、CometActor トレイトを継承する。PDF との内容をほぼそのまま流用する。

package sandbox.comet.comet
import _root_.net.liftweb.http.CometActor
class Messages extends CometActor

defaultPrefix をオーバーライドしておく。<lift:comet>の内側で、処理対象とするタグの接頭子を指定する模様。
Full は Box の子クラスであり、Box は Option と同じ目的のものだが、いくつか機能が追加されている*2。None に対応するのは Empty。
ちなみに、scala では import をもっと短くかけるのだが、Lift 学習中なのであえてFQCNで書いている。

import _root_.net.liftweb.util.Full

class Messages extends CometActor {
  override def defaultPrefix = Full("msg")
}

続いて、render メソッドを追加する。<lift:comet/>タグのレンダリング時に呼び出されるので、<msg:time/>を span タグで囲まれた日時に置き換える。span タグにはあとで部分的な更新ができるように id 属性で Id を設定しておく。

import _root_.net.liftweb.util.Full
import _root_.net.liftweb.util.TimeHelpers.now

class Messages extends CometActor {
  override def defaultPrefix = Full("msg")
  def render = bind("time" -> <span id="time">{now}</span>)
}

一度、この状態で動かしてみる。ページにアクセスした時点での日時が表示されるはず。

1秒毎に更新する

1秒毎に Messages アクタに対してメッセージを送信して、現在時刻の表示を更新する。アクタ開始時に呼び出される localSetup で、1 秒後にメッセージが届くようにスケジュールしておく。
また、lowPriority メソッドをオーバーライドして、メッセージが届くたびに現在日時を更新する。更新処理は SetHtml によって指定された要素にテキストノードを設定することで実施している。SetHtml は JsCmd の一つであり、クライアント側で Javascript を実行するものと思われる。

package sandbox.comet.comet

import _root_.net.liftweb.http.CometActor
import _root_.net.liftweb.http.js.JsCmds.SetHtml
import _root_.net.liftweb.util.ActorPing
import _root_.net.liftweb.util.Full
import _root_.net.liftweb.util.TimeHelpers.now
import _root_.net.liftweb.util.Helpers.TimeSpan
import _root_.scala.xml.Text

class Messages extends CometActor {
  override def defaultPrefix = Full("msg")
  override def render = bind("time" -> <span id="time">{now}</span>)

  override protected def localSetup = ActorPing.schedule(this, 'update, TimeSpan(1000))
  override def lowPriority: PartialFunction[Any, Unit] = {
    case 'update => {
      partialUpdate(SetHtml("time", Text(now.toString)))
      ActorPing.schedule(this, 'update, TimeSpan(1000))
    }
  }
}

やっていることは PDF の内容となんら変わらないので、細かい部分は PDF を参照。


次はログインなし、ログなしで、メッセージを送りあうだけのチャットをつくる予定。

*1:http://groups.google.com/group/the-lift-book

*2:Emptyのときに失敗メッセージに変換するとか