- サーバサイドでステータス更新をFIFOに溜め込む
- クライアントからリクエストをブロック(long poll)しFIFOからステータスを送出する
- クライアントサイドでlong pollを行い画面を更新する
- 再度サーバへリクエストを投げる
ここで使える!と思ったのがmultipart/mixedです。
multipart/mixedはboundaryで区切られたボディ部を繰り返し送出する際に用いられる仕組みで、実はXHR(XMLHttpRequest)はこのmultipart/mixedなレスポンスを受け取る事が出来る。
受け取るとは言っても、実際にはreadyStateが変化する...でしかないのだけど、これを扱うのに良いライブラリがある。
diggで使われているDUI.jsとStream.jsです。
digg's stream at master - GitHubこのstreamライブラリは内部でreadyStateが3になるのを待ち、タイマを張ってboundaryを監視するものです。つまり1 body部を扱う事になるので、使う際にはmime typeを指定する事になります。
Alpha repository for DUI.Stream. When it stabilizes, it will be merged back into DUI
http://github.com/digg/stream
var s = new DUI.Stream();
s.listen('text/html', function(payload) {
...
});
s.load('/push');
この仕組みを使って、twitterからのストリームをクライアントにmultipart/mixedで送出し、かつクライアントも接続を保持したままレスポンスを受け取るのです。接続を切らないので、ほぼタイムロス無しのリアルタイムと言って良いでしょう。今日はこれをsinatraを使って実装してみました。出来れば一度動かしてみて下さい。これまでリクエストを繰り返し送っていたアプリケーションと比べてスムーズにデータを受け取っているのが分かって頂けるかと思います。
上記diggのライブラリを作業フォルダのstatic/jsフォルダ配下に置き、以下のファイル(fast-twitter-stream.rb)を作業フォルダ直下に置きます。
require 'rubygems'
require 'sinatra'
require 'json'
require 'tweetstream'
require 'pit'
# doesn't work on thin and webrick
set :server, 'mongrel'
set :public, File.dirname(__FILE__) + '/static'
config = Pit.get("twitter.com", :require => {
"username" => "your username in twitter",
"password" => "your password in twitter"
})
get '/' do
<<HTML
<html> <head>
<title>Server Push</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js"></script>
<script type="text/javascript" src="/js/DUI.js"></script>
<script type="text/javascript" src="/js/Stream.js"></script>
<script type="text/javascript">
$(function() {
var s = new DUI.Stream();
s.listen('text/javascript', function(payload) {
var status = eval(payload);
$('#content').append('<p>' + status.text + '</p>');
});
s.load('/push');
});
</script>
</head>
<body>
<h1>Server Push</h1>
<div id="content"></div>
</body>
</html>
HTML
end
get '/push' do
boundary = '|||'
response['Content-Type'] = 'multipart/mixed; boundary="' + boundary + '"'
MultipartResponse.new(boundary, 'text/javascript')
end
class MultipartResponse
def initialize(boundary, content_type)
@boundary = boundary
@content_type = content_type
end
def each
TweetStream::Client.new(config[:username], config[:password]).sample do |status|
yield "--#{@boundary}\nContent-Type: #{@content_type}\n(#{status.to_json})"
end
end
end
元となったコードはyoupyさんの物を参考にさせて頂きました。ブラウザから"http://localhost:4567/"にアクセスすると、もの凄い勢いでステータスが追加されていくのが分かるかと思います。しかも切断していないのでかなり高速です。
お試しの効果には個人差があります。
このmultipart/mixed、結構使えるなーと思いました。得にリアルタイムな画像ストリーミングとして送出したり、リアルタイム電光掲示板の様な物にも使えるかと思います。
今回のサンプルもgithubに置いてあります。
mattn's fast-twitter-stream at master - GitHubよろしければご参考までに。
fast twitter stream client using multipart/mixed.
http://github.com/mattn/fast-twitter-stream