2013/04/16

[heroku][node.js][Express]TwitterStreamingAPIを利用したサンプル

とりあえず前回deployまでは試してみたのでExpressでTwitterのStreamingAPIを利用してツイートを垂れ流してみました。

ファイル構成としては以下のようになっています。
  • routes - index.js
  • views - index.ejs
  • .gitignore
  • app.js
  • package.json
  • Procfile

/**
 * Node.js Twitter Streaming API Sample
 */

var express = require('express')
  , routes = require('./routes')
  , https = require('https')
  , socketIO = require('socket.io');

//twitterのAPI情報
var twitterApi = {
 host : 'stream.twitter.com',
 port : 443,
 path : '/1/statuses/filter.json?language=ja&locations=127.441,25.720,150.820,46.679',
 auth : 'XXX_username_XXX:XXX_password_XXX'
};

//Server設定
var app = express.createServer();
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.get('/', routes.index);

//Herokuの環境変数よりポート番号を取得、localhostでは3000を使用
var io = socketIO.listen(app.listen(process.env.PORT || 3000));
//HerokuではWebSocketが利用できないためpollingで対応 
io.configure(function () {
  io.set("transports", ["xhr-polling"]); 
  io.set("polling duration", 10); 
  io.set('log level', 1);
});

//Twitter から Streaming API で取得したデータを
//クライアント側へ Socket.IO 経由で送信
var req = https.get(twitterApi).on('response', function(res) {
 res.on('data', function(chunk) {
  try{
   var tweet = JSON.parse(chunk);
   io.sockets.emit('tweet', {
    id: tweet.id,
    name: tweet.user.name,
    text: tweet.text,
    time: tweet.created_at,
    img : tweet.user.profile_image_url,
    place : tweet.place
   });
  } catch(e) { 
   console.log('-----response ERROR-----');
   console.log(e);
   console.log(chunk);
  }
 });
}).on('error', function(e){
 console.log(e);
});

//例外処理
process.on('uncaughtException', function (err) {
 console.log('uncaughtException => ' + err);
});
最初の5行目からは読み込むモジュールを記述。expressとroutesはさておき、TwitterのAPIを読み込むためhttpsモジュールを追加します。
socket.ioについて、node.jsで双方向通信を行うため非常に便利なモジュールです。具体的にはIE9などWebSocketをサポートしていないブラウザに対して代替手段を用いてリアルタイム通信を実現してくれるモジュールです。(WebSocket、Adobe® Flash® Socket、AJAX long polling、AJAX multipart streaming、Forever Iframe、JSONP Pollingの順序で使用中のブラウザが対応する通信手段を識別します)
10行目からはTwitterAPIのエンドポイント等の設定です。今回は位置情報を条件にセットし、日本語のツイートのみ対象としています。またStreamingAPIではベーシック認証を利用するのでUser/Pwは適せん変更してください。
19行目からはServerにアクセスしてきた時のサーバー情報です。サンプルは1ページしかないのでroutesのindex.jsを利用する必要はあまりないのですが今後の拡張を考えExpressの方式どおりrenderします。
25行目からはsocket.ioの設定です。herokuの環境変数を読み込み、存在しない場合はport:3000で接続します。(localhostの場合などで利用) またsocket.ioの通信方式について、heroku(Cedar stack)では内部のリソース制御のためか、xhr-pollingのみ利用可能となっています。詳細は公式を参照してください。
35行目からはTwitterのデータを取得しクライアントに送信しています。socket.ioではemitメソッドを利用してイベントを発生させ、クライアント側で検知して処理を行います。第1引数がイベント名、第2引数がデータとなります。
57行目の例外処理は、try~catchで検知できなかった場合のエラー処理となります。エラーでクラッシュした場合、サーバを稼動させ続けるためにこのような記述をしています。
exports.index = function(req, res){
  res.render('index', {});
};

<!DOCTYPE html>
<html>
<head>
<title>Node.js Twitter Streaming API Sample</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Style-Type" content="text/css">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
<style type="text/css">
* {
    margin: 0;
    padding: 0;
}

div.media{
    border-bottom:1px solid #ddd;
    background-color: #ffffff;
    margin:0;
    padding:10px;
    font-size:10pt;
    line-height: 15px;
}

pre {
    padding:0;
    margin:0;
    border:none;
    background-color:#ffffff;
}
span.tweet_time,span.tweet_place{
    margin:0px 20px;
    color:#999999;
}
</style>
</head>
<body>
    <h1>Node.js Twitter Streaming API Sample</h1>
    <div id="tweetWrap">
        <script id="tweet" type="text/x-jquery-tmpl">
            <div class="media" id="${feedId}">
                <a class="pull-left" href="javascript:void(0)">
                    <img class="media-object" width="45px" src="${userImg}" />
                </a>
                <div class="media-body">
                    <h5 class="media-heading">${userNm}</h5>
                    <pre> ${body}</pre>
                    <span class="tweet_time">${time}</span>
                    <span class="tweet_place">${place}</span>
                </div>
            </div>
        </script>
    </div>
   
    <script src="/socket.io/socket.io.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <script src="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
    <script src="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/js/bootstrap.min.js"></script>
    <script>
    $(function(){
        // ioオブジェクトチェック
        if (typeof io=="undefined") {
            $("#tweetWrap").html("<b>ioオブジェクトが作成できません[socket.io.jsエラー]</b>");
            return;
        }

        //サーバーに接続
        var socket = io.connect('/', {
            'reconnect': true,
            'reconnection delay': 1000,
            'reconnection limit': 1000,
            'max reconnection attempts': Infinity,
        });

        //ツイート受信のタイミングでフィードを追加
        socket.on('tweet', function(tweet) {
           var tweet ={
                feedId:tweet.id,
                userImg:tweet.img,
                userNm:tweet.name,
                body:tweet.text,
                time:tweet.time,
                place:(typeof tweet.place!="undefined")?tweet.place.full_name:""
            };
            //受信フィードを表示
            $("#tweet").tmpl(tweet).prependTo("#tweetWrap").hide().show("fast");
            //15件を超えたらフィードを削除
            (15<$("#tweetWrap").children().length) && $("div.media:last").remove();
        });
    });
    </script>

</body>
</html>

クライアント側の流れとしては、ツイートデータを受信したらjquery-tmplを利用して画面に追加しています。また見栄えをちょっとよくする為にbootstrapも利用しています。データはどんどん流れてくるため15件を超えたら一番古いデータ(一番下にあるデータ)を消しています。
57行目でsocket.ioのクライアント側のライブラリを読み込んでいます。この時点でioオブジェクトが作成されます。63行目で念のためioオブジェクトの存在チェックを行っています。
70行目でsocket.ioでサーバへの接続および再接続方法の設定を行っています。現在の設定は再接続は1秒毎に行い、無制限に再接続処理を行います。
※オプション詳細はこちら
78行目ではサーバでemitにより発生させたイベントを検知しています。検知の際はonメソッドを利用します。後はjquery-tmpl用にデータをパースして画面に表示します。(直接jquery-tmplに渡す形でデータ設計をしても良かったのですが、分かりやすくするためちょっと変えました)
{
  "name": "nodejs_twitter_steraming",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "express": "3.1.1",
    "ejs": "*",
    "socket.io": "0.9.6"
  },
  "engines": {
    "node": "0.8.x",
    "npm": "1.1.x"
  }
}
socket.ioを追記しています。
node_modules
localでnpm installを実行した場合、node_modulesにモジュールファイルがインストールされます。これらをherokuにアップロードしないために当ファイルを作成します。
実際のサンプルはこちらです。これらのソースはGitHubに公開しています。 これまでのようにsocket.ioを利用する事で簡単にリアルタイム通信が実装できる事が分かりました。 またちょっと応用してこんなのを作ってみました。次回は双方向を試してみます。

0 件のコメント:

コメントを投稿