ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

IE 10 の拡張された XHR を使って HTTP のストリーミングを受信する (Twitter Streaming API とか)

Web ブラウザから web サーバーに HTTP リクエストを送る際によく用いられる XMLHttpRequest (XHR) ですが、XMLHttpRequestAPI を見ると、chunked transfer encoding による HTTP のストリーミングからのデータを受信して、都度処理をしていく、というような使い方ができるようにはなっていないと思います。

Internet Explorer 10 (IE 10) では、そういうことができるように 拡張された XMLHttpRequest が実装されています ので紹介します。

少ないデータ量なら COMET Streaming で対応できるが...

もちろん、通常の XMLHttpRequest でも chunked transfer encoding なデータを受け取ることはできますし、データの受信が進むたびに progress イベントが発生しますので、受け取るデータが少量の場合はなんとかなります。 詳しくは以下のページをご覧ください。

しかしながら、受け取ったデータは XMLHttpRequest の接続が終了するまで XMLHttpRequest オブジェクトの中にため続けられますので、長時間 HTTP 接続を保ったまま、送られてきたデータを都度処理していく というようなことができません。 データがどんどんたまっていきますので、やがてメモリ不足になってしまいます。

具体的には、Twitter が Streaming API を chunked transfer encoding で送ってきます ので、XMLHttpRequestTwitter Streaming API を扱いたい場合に困りますね。

IE 10 の拡張された XHR なら HTTP のストリーミングも扱える

まあ普通のブラウザ上で Twitter の Streaming API なんて使えなくてもいいでしょ、って感じもしますが、最近はブラウザの拡張機能やら Windows ストアアプリ (旧称 Metro アプリ) やらで JavaScript から Twitter Streaming API を使いたくなることがしばしばあります。 というわけで、Windows ストアアプリを JavaScript で書くときに使える IE 10 の拡張された XMLHttpRequest を使って、chunked transfer encoding で送られてきたデータを都度処理していく方法について書いておきます。

HTTP ストリーミングを扱う方法

HTTP ストリーミングを扱うためには、リクエストを送る前に、XMLHttpRequest#responseType プロパティに "ms-stream" を設定します。

ダウンロード要求の responseType プロパティが "ms-stream" に設定されている場合、次の例に示すように、実行中にコンテンツを操作できます。

http://msdn.microsoft.com/ja-jp/library/ie/hh673569%28v=vs.85%29.aspx
レスポンスの受け取り

次に、レスポンスの受け取り方について説明します。 readyStateChange イベントで XMLHttpRequest#readyState プロパティが XMLHttpRequest.LOADING になったときにストリーミングを受け取れるようになっていますので、その中でストリーミングの処理を開始します。 ストリーミングで受け取ったデータは、XMLHttpRequest#response プロパティの値に入っている MSStream オブジェクトで表されます。

MSStream オブジェクトからデータを取り出す方法

MSStream オブジェクトは、Windows.Storage.Streams.IInputStream インターフェイスを実装したオブジェクト (IInputStream オブジェクト) をラップしたもので、実際のストリームは IInputSteram オブジェクトで表されます。

MSStream オブジェクトからデータを読み出す方法としては MSStreamReader を使う方法もありますが、どうやら MSStreamReader ではデータを受信する都度データを読み出す、というようなことができない (すべてのデータを読み込んでから最後に読み出すしかできない) ようだったので、MSStream オブジェクトではなく、その下にある IInputStream オブジェクトを直接操作してデータを読み出すしかなさそうです。

MSStream のドキュメントには MSStream#msInputStream プロパティ というものが存在しますが、手元 (Windows 8 RTM 版) で試したところそのようなプロパティは存在せず、代わりに MSStream#msDetachStream メソッドというのが存在しました。 このメソッドを使うと、MSStream オブジェクトとその下にある IInputStream オブジェクトが切り離され、切り離された IInputStream オブジェクトを返り値として得ることができます。 IInputStream オブジェクトの readAsync メソッド を使うことで *1、データを受け取るたびにデータの読み出しを行って処理を進めることができます。

サンプルコード

Windows ストアアプリ開発時に使えるサンプルコードとして、WinJS.xhr (XMLHttpRequest を扱う便利関数) で HTTP ストリーミングを扱う例を示します。

var uri = "http://www.example.com/test_stream";
WinJS.xhr({ url: uri, responseType: "ms-stream" }).
then(function (req) {
    // 完了したときの処理
    // ...
}, function onError(err) {
    // エラーが発生した時の処理
    // ...
}, function onProg(req) {
    // streaming の読み込みを開始した時に以下の if 文の中に入る
    if (req.readyState === req.LOADING) {
         // res は MSStream オブジェクト
         var res = req.response;
         // msDetachStream メソッドを呼び出すことで IInputStream オブジェクトを res から切り離して取り出す
         var stream = res.msDetachStream();
         // さらに stream からデータを読み込む処理に移る
         // ...
    }
}).
done(null, function (err) {
    // 例外発生時の処理
    // ...
});

余談 : その他のストリーミング関係の話

JavaScriptTCP/IP 上のストリーミングを扱うならば、HTTP の chunked transfer encoding を使うよりも以下の 2 つのどちらかを使う方が便利な気がします。 Twitter Streaming API のようにサーバー側がすでに存在している場合はどうしようもないですが、新たに (サーバー側も含めて ) Twitter Streaming API みたいなものを実装するのであれば、Server-Sent Events を使うのが良さそうです。

  • Server-Sent Events : HTTP 上で動作; サーバーからの push 配信を受け取る
  • WebSocket : TCP 上で動作; クライアントとサーバーの双方向通信

*1:もしくは IInputStream からデータを読み出すためのオブジェクトを操作することで