cometネタ

なんとか動いた

cometで何か動くものを作ってみようと思っていたのですが,ネタを思いつかなくて忘れていました.で,ふとクライアントの時刻じゃなくて,サーバから時刻を配信するツールを作れば簡単なサンプルになるんじゃないかと思いつきました.win上のIE6とFF2,mac上のsafariとFF2で動くところまできたので,とりあえずまとめてみます.

サーバ系

参考にしたのはPoCo::HTTP で Comet チャットサーバを作る - daily dayflower2006-11-20です.っていうか,ほとんどそのまま.いずれはcomet用とPODに記載があるSprocketベースで作り直そうと思っていますが,今のところはPOE::Component::Server::HTTPベース.元のスクリプトと違うところは,1秒ごとにクライアントに対して時刻データをJSON形式で送っているところです.このサーバは,http://localhost:8088/fileにアクセスするとhtmlデータを返却し,http://localhost:8088/にアクセスすると,以下のような形式で時刻データを返します.

JsonCallback({"time":"時刻","callback":"servertime"})

特徴は,何がなんでもクライアントにキャッシュさせないようがんばってるところ,でしょうか.

#!/usr/local/bin/perl

use warnings;
use strict;
use POE qw(Component::Server::HTTP);
use HTTP::Status;
use Scalar::Util qw(refaddr);
use DateTime;

my $server = POE::Component::Server::HTTP->new(
    Port           => 8088,
    ContentHandler => {
        '/file' => \&rwfile,
        '/'     => \&caller,
    },
    PostHandler  => { '/' => [ \&cleanup ], },
    ErrorHnadler => { '/' => \&clearnup, },
);

POE::Session->create(
    inline_states => {
        _start => sub {
            $_[HEAP]->{next_alarm_time} = int( time() ) + 1;
            $_[KERNEL]->alarm( tick => $_[HEAP]->{next_alarm_time} );
        },

        tick => sub {
            my $dt = DateTime->now( time_zone => 'Asia/Tokyo' );
            my $str =
                qq[JsonCallback({"time":"]
              . $dt->ymd . qq[ ]
              . $dt->hms . qq[", "callback":"servertime"})];
            broadcast_message($str);
            $_[HEAP]->{next_alarm_time} += 1;
            $_[KERNEL]->alarm( tick => $_[HEAP]->{next_alarm_time} );
        },
    },
);

POE::Kernel->run();

exit();

my %receiver;

sub rwfile {
    my ( $req, $res ) = @_;
    my $file;
    $res->code(RC_OK);
    open FILE, "./test.html" or die "Can't open file\n";
    {
        local $/;
        $file = <FILE>
    }
    $res->content($file);
    return RC_OK;
}

sub caller {
    my ( $req, $res ) = @_;
    $receiver{ refaddr $res} = $res;
    $req->headers->header( Connection => 'close' );
    return RC_WAIT;
}

sub cleanup {
    my ( $req, $res ) = @_;
    delete $receiver{ refaddr $res};
}

sub broadcast_message {
    my ($message) = @_;
    for my $res ( values %receiver ) {
        $res->code(RC_OK);
        $res->content_type('text/plain');
        $res->headers->header( CacheControl => 'no-cache' );
        $res->headers->header( Expires      => '-1' );
        $res->content( $message . "\n" );
        $res->continue();
    }
}

クライアント系

クライアントで参考にしたのは,Life is Beautiful: Live Page-View Counter, Comet server and JSON-push.こちらもほぼそっくりそのまま使っています.違っているのは,元の記事では外部呼び出しになっているJSON and the Dynamic Script Tag: Easy, XML-less Web Services for JavaScriptのjsr_class.jsを,動きを調べるためにhtmファイルに組み込んでしまったことぐらい.サーバから受け取ったデータをJSONPとして該当するメソッドにdispatchするところの作り方が今後の課題かなと思ってます.

<html>
<head>
<script type = "text/javascript">

var JSONscriptRequest = function(fullUrl){
  this.fullUrl = fullUrl;
  this.noCacheID = '&noCacheID=' + (new Date()).getTime();
  this.headLoc = document.getElementsByTagName("head").item(0);
  this.scriptId = 'JscriptId' + this.scriptCounter++;
}

JSONscriptRequest.scriptCounter = 1;

JSONscriptRequest.prototype.buildScriptTag = function () {

  this.scriptObj = document.createElement("script");
  this.scriptObj.setAttribute("type", "text/javascript");
  this.scriptObj.setAttribute("charset", "utf-8");
  this.scriptObj.setAttribute("src", this.fullUrl + this.noCacheID);
  this.scriptObj.setAttribute("id", this.scriptId);
}
 
JSONscriptRequest.prototype.removeScriptTag = function () {
  this.headLoc.removeChild(this.scriptObj);  
}

JSONscriptRequest.prototype.addScriptTag = function () {
  this.headLoc.appendChild(this.scriptObj);
}
</script>
<script type="text/javascript">

var st;

function servertime(jsonData) {
  document.form1.text1.value = jsonData.time;
  reconnectComet();
}

function JsonCallback(jsonData){
  switch(jsonData.callback){
    case "servertime" : servertime(jsonData); break;
    default : alert("unknown JsonCallback");
  }
}

function connectComet() {
  var req = 'http://foo.bar.co.jp:8088/';
  st = new JSONscriptRequest(req); 
  st.buildScriptTag(); 
  st.addScriptTag();
}

function reconnectComet() {
  st.removeScriptTag();
  connectComet()
}

</script>
</head>
<body onLoad="connectComet()">
<form name=form1>
<input type=text name=text1 size=60>
</form>
</body>
</html>

まとめ

動きは,

  1. クライアントがhttp://foo.bar.co.jp:8088/file にアクセスしてhtmlデータを受信
  2. onLoadイベントで呼び出されたメソッドから,http://foo.bar.co.jp:8088/ にアクセス
  3. サーバからJSONPデータを受信
  4. JSONPで呼び出されたメソッドから,http://foo.bar.co.jp:8088/ にアクセス
  5. サーバからJSONPデータを受信
  6. ...

というのを繰り返しています.動きが変,とか,ここを変えるともっと美しくなる,とかコメントやトラックバックがありましたら,お寄せいただけると幸いです.ちなみに,サービスとしてみると,何のひねりもなくて面白くもなんともないので,次はちょっと遊べるものを作ろうと思っています.