愛と勇気と缶ビール

ふしぎとぼくらはなにをしたらよいか

ゆとりがlong pollingを実装してみた

long pollingって結局

  • HTTPでつなぎに行って、レスポンスが帰ってきたら何かして再度つなぐ(Client Side)
  • HTTPリクエストをキープしといて、イベントに応じてレスポンスを好きな時に返す(Server Side)

の2つだろう、と思って、勉強を兼ねて「とりあえず上記の条件だけは満たすっぽいもの」を書いてみた。実際はこんな風にはかかないだろうし、エラーハンドリングとかをもっとちゃんとする必要があると思われる。(特にtimeout処理とか)

Server side ( longpoll.js )

node v0.3.1-pre にて動作確認

var server = require('http').createServer(handle),
    util = require('util'),
    fs = require('fs'),
    port = 8000,
    listeners = [],
    pub = "/pub",
    sub = "/sub",
    script = "/script/longpollclient.js";


function grub(request, response) {
  request.pause();
  listeners.push( { "req": request, "res": response } );
}

function publish(request, response) {
  var listener;

  request.on("data", function(data) {
    while( listener = listeners.pop() ) { 
      listener.req.resume();
      listener.res.writeHead(200, { 'Content-Type': 'application/json' }); 
      listener.res.end(data);
    }   
  }); 

  response.writeHead(200, { 'Content-Type': 'text/plain' }); 
  response.end('sent');
}

function serveStatic(filename, ctype, response) {
  fs.readFile(filename, function(err, data) {
    if (err) {
      console.log(err);
    }   
    else {
      response.writeHead(200, { 'Content-Type': ctype }); 
      response.end(data);
    }   
  }); 
}

function serveClientJS(response) {
  serveStatic('./longpollclient.js', 'text/javascript', response);
}

function serveHtml(response) {
  serveStatic('./index.html', 'text/html', response);
}

function handle(request, response) {

  // http://hostname:port/sub
  if ( request.url.match(sub) ) {
    grub(request, response);
  }
  // http://hostname:port/pub
  else if ( request.url.match(pub) ) {
    publish(request, response);
  }
  // http://hostname:port/script/longpollclient.js
  else if ( request.url.match(script) ) {
    serveClientJS(response);
  }
  else {
    serveHtml(response);
  }

}

server.listen(port);
console.log("server listening " + port + "...");

Client Side ( longpollclient.js, index.html )

Firefox 3.6.12 にて動作確認(他では試してないよ。xhrがクロスブラウザじゃない!とか言われても困るよ)

var longPoll;

(function(XHR) {

  // public interfaces

  function LongPoll(base) {
    this.basepath = base;
  }

  LongPoll.prototype.subscribe = subscribe;
  LongPoll.prototype.publish = publish;
  longPoll = LongPoll;

  function subscribe(path, onmessage) {
    var url = this.basepath + path;
    sub(url, onmessage);
  }

  function publish(path, json) {
    var url = this.basepath + path;
    pub(url, json);
  }


  // implementations

  function pub(url, data) {
    var xhr = new XHR();
    xhr.open("POST", url, true);
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.send(JSON.stringify(data));
  }

  function sub(url, onmessage) {
    var xhr = new XHR();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function handleXhr() {
      if ( xhr.readyState === 4 && xhr.status === 200 ) { 
        onmessage(JSON.parse(xhr.responseText));
        sub(url, onmessage);
      }   
    };  
    xhr.send();
  }

})(XMLHttpRequest);
<!DOCTYPE HTML>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>long polling test</title>
</head>
<body>
  <script type="text/javascript" src="script/longpollclient.js"></script>
  <script type="text/javascript">
    var poll = new longPoll("http://" + location.host);

    poll.subscribe('/sub', function(message) {
      console.dir(message);
    });

    setInterval(function() {
      poll.publish('/pub', {"foo": "bar" });
    }, 5000);
  </script>
</body>
</html>


一応、それっぽい動きはする。