2011/03/03


こんばんわ。node.jsやってないとjavascriptのもぐりだと言われている昨今、いかがお過ごしでしょうか。
個人的には node.js は2回くらい飽きてしまって、3周目くらいです。

よく考えたらこのブログでは一度も node.js に触れてなかったなーと思ったのと、最近触ってないから忘れてそうだな...という思いからエントリにしてみました。
node.js が面白いと言われている理由の一つに非同期処理があります。そして非同期を面白くする題材として websocket があります。今日はその websocket を使って、サンプルを作ります。
物としては、twitter の filter stream から、instagr.ampicplz.com の画像URLを収集し、それをクライアントにブロードキャストします。クライアントはそれを受けて Growl 風にポップアップ表示するという物です。
まずサイトに仕上げるには、静的ファイルサーバが必要になります。
どのサイトでも紹介してそうですが、適当ながら動くレベルの物を作ります。
var sys = require('sys'),
    fs = require('fs'),
    url = require('url'),
    http = require('http');

// static file server
var extmap = {'.htm''text/html''.css''text/css'};
var server = http.createServer(function(req, res){
  var uri = url.parse(req.url).pathname;  
  if (uri.match('/$')) uri = '/index.html'
  try {
    var filename = __dirname + '/static' + uri;
    var contenttype = extmap[filename.substr(filename.lastIndexOf("."), 4)] || 'application/octet-stream';
    var rs = fs.createReadStream(filename);
    res.writeHead(200, {'Content-Type': contenttype});
    sys.pump(rs, res);
  } catch(e) {
    console.log(e);
    res.sendHeader(404, {"Content-Type""text/plain"});  
    res.write("Not Found\n");  
    res.close();  
  }
});
server.listen(8080);
これを server.js というファイル名で保存して # node server.js と実行すると、static フォルダ配下のファイルが http://localhost:8080/ でサーブされる仕組みです。
次に、websocket を扱える様にします。既存のサーバ上に websocket を乗せるには以下の様にサーバを指定すればokです。(おそらくこれは sugyan さんへのヒントになるのかもしれませんが)
var ws = websocket.createServer({server: server});
クライアント側の実装だとこんな感じですね。
var connection = new WebSocket('ws://' + location.host);
connection.onopen = function(event) {
}
connection.onmessage = function(event) {
}
さて、下準備は出来たので twitter stream を扱いましょう。twitter stream API はまだ basic 認証で扱えます。http-basic-auth モジュールを使いましょう。なお、Ubuntu 向けに launchpad から提供されている nodejs には node-waf が入っていませんので、依存で入る base64 モジュールのビルドに失敗します。どうしても試したい人は nodejs をソースからビルドしましょう。
twitter filter stream を扱うコードは以下の様になります。
var basicauth = require('http-basic-auth');

var basicauthclient = basicauth.createClient(80'stream.twitter.com'falsefalse, account)
var req = basicauthclient.request('GET''/1/statuses/filter.json?track=picplz,instagr', {'host''stream.twitter.com'})
req.end();
req.on('response'function (res) {
  res.on('data'function(chunk) {
  })
})
account は username と password をキーに持つオブジェクトです。ご自分のアカウントを設定しましょう。
twitter stream を受信したら、tweet の entities から URL 一覧を取得し、instagr.am と picplz.com の画像URLを取得しましょう。picplz.com はURLの後ろに /thumb/400 を足すだけですが、instagr.am は API にアクセスしなければなりません。
tweet 受信時の処理は以下の様になりました。
req.on('response'function (res) {
  res.on('data'function(chunk) {
    try {
      var tweet = JSON.parse(chunk);
      // parse entities urls
      [].forEach.call(tweet.entities.urls, function(item) {
         // pick images of instagr.am or picplz.com
         if (item.url.match('^http://instagr.am/p/')) {
           var client = http.createClient(80'instagr.am');
           var req = client.request('GET''/api/v1/oembed/?format=json&maxheight=330&url=' + item.url, {'host''instagr.am'});
           req.on('response'function(res){
             res.on('data'function(chunk){
               var url = JSON.parse(chunk).url
               console.log(url)
               ws.broadcast(url)
             });
           });
           req.end();
         }
         if (item.url.match('^http://picplz.com/')) {
           var url = item.url + '/thumb/400'
           console.log(url)
           ws.broadcast(url)
         }
      });
    } catch(e) { console.log(e) }
  });
});
サーバ部は出来上がりですね。最後にクライアント部を作りましょう。
$(function() {
  var connection = new WebSocket('ws://' + location.host);
  connection.onopen = function(event) {
    $('#fotoflo').empty();
  }
  connection.onmessage = function(event) {
    var x = (Math.random() * ($(document).width() - 330)).toFixed();
    var y = (Math.random() * ($(document).height() - 330)).toFixed();
    var item = $('<div/>')
      .addClass('popup-image')
      .css({'left': x + 'px''top': y + 'px''position''absolute''display''none'})
      .appendTo('body')
    $('<img/>')
      .attr('src'event.data)
      .bind('load'function() {
        $(item).fadeIn(500).delay(10000).fadeOut(500function() {
          $(this).remove()
        })
      }).appendTo(item)
  }
})
こんな感じでしょうか。受信する度にランダムな位置へ画像をフェイドイン表示し、数秒経過したら消すというオーソドックスな物です。
以下、リポジトリの場所は示しますが、サーバの全体ソースを載せておきます。
var sys = require('sys'),
    fs = require('fs'),
    url = require('url'),
    http = require('http'),
    websocket = require('websocket-server'),
    basicauth = require('http-basic-auth');

// load twitter account
var account = JSON.parse(fs.readFileSync(__dirname + '/config.json''utf8'))

// static file server
var extmap = {'.htm''text/html''.css''text/css'};
var server = http.createServer(function(req, res){
  var uri = url.parse(req.url).pathname;  
  if (uri.match('/$')) uri = '/index.html'
  try {
    var filename = __dirname + '/static' + uri;
    var contenttype = extmap[filename.substr(filename.lastIndexOf("."), 4)] || 'application/octet-stream';
    var rs = fs.createReadStream(filename);
    res.writeHead(200, {'Content-Type': contenttype});
    sys.pump(rs, res);
  } catch(e) {
    console.log(e);
    res.sendHeader(404, {"Content-Type""text/plain"});  
    res.write("Not Found\n");  
    res.close();  
  }
});
server.listen(80);

// listen websocket server on the server
var ws = websocket.createServer({server: server});

// twitter filter stream
var basicauthclient = basicauth.createClient(80'stream.twitter.com'falsefalse, account)
var req = basicauthclient.request('GET''/1/statuses/filter.json?track=picplz,instagr', {'host''stream.twitter.com'})
req.end();
req.on('response'function (res) {
  res.on('data'function(chunk) {
    try {
      var tweet = JSON.parse(chunk);
      // parse entities urls
      [].forEach.call(tweet.entities.urls, function(item) {
         // pick images of instagr.am or picplz.com
         if (item.url.match('^http://instagr.am/p/')) {
           var client = http.createClient(80'instagr.am');
           var req = client.request('GET''/api/v1/oembed/?format=json&maxheight=330&url=' + item.url, {'host''instagr.am'});
           req.on('response'function(res){
             res.on('data'function(chunk){
               var url = JSON.parse(chunk).url
               console.log(url)
               ws.broadcast(url)
             });
           });
           req.end();
         }
         if (item.url.match('^http://picplz.com/')) {
           var url = item.url + '/thumb/400'
           console.log(url)
           ws.broadcast(url)
         }
      });
    } catch(e) { console.log(e) }
  });
});
簡単ですね!
imagestream
server.js と同じフォルダに config.json というファイルがあるので、そのファイル内を twitter アカウントで修正して頂ければ動きます。
mattn/node-image-stream - GitHub

display images from twitter stream

https://github.com/mattn/node-image-stream
みなさんもぜひ面白い物作ってみて下さい。
Posted at by