ReadableStreamをストリーミングしながらダウンロードできるStreamSaver.jsの仕組みを読み解きたい
#StreamSaver.js #JavaScript #WebブラウザのJavaScript #Service_Worker
はじめに
ReadableStreamをストリーミングしながらダウンロードしたい。
残念ながら、ブラウザに提供されている標準に機能にはなさそう。
そして探してみるとStreamSaver.jsが見つかる。
でも、どうやってブラウザにある機能だけで実装しているかがとても気になったので、ソースコードから仕組みを読み解きたくなった。
なぜストリーミングしながらのダウンロードをしたいか?
1GBとか1TBとかのファイルだったらメモリに置くのは、好ましくないかつ現実的ではないので、ストリーミングしながらダウンロードしたい。
ブラウザ標準でこの機能が実装されることが一番嬉しい。Streams APIかFile APIにも盛り込まれてほしい。
小さいデータなら、全部読み切ってBlobにしてBlob URLを生成してopenとかすればダウンロードはできる。
使われている技術
Service Worker
MessageChannel
最終的にService WorkerのrespondWith()でダウンロードされる。
MessageChannelはiframe内のJSにReadableStreamを渡すために使われる。
どのソース
コミットが28fbafea12da53cbef1f0e3624260204236cfcfd時点のコード。
パッケージ外のリソース
StreamSaverだけで完結してはいなさそう。
完結してないの意味は、以下のコードのソースはリポジトリ上にあるがポータブルではないという意味(例えば、GitHub Pagesがダウンすると使えなくなるはず)。
以下の作者さんのGitHub PagesがHTTPSになることを利用している様子。
Service Worker: https://jimmywarting.github.io/StreamSaver.js/sw.js
mitm.html iframeに埋め込まれる:https://jimmywarting.github.io/StreamSaver.js/mitm.html?version=1.2.0
ping.html iframeに埋め込まれる:https://jimmywarting.github.io/StreamSaver.js/ping.html?version=1.2.0'
mitmは"man in the middle"の意味らしい。
(対応ソース)
ストリーミングでダウンロードできる仕組み
ざっと把握した感じだと、
最終的にService Wokerのevent.respondWith(new Response(stream))のstreamにダウンロードしたいReadableStreamが渡ることでダウンロードされる。
(対応ソース)
一般的にはrespondWith()はキャッシュしていたファイルを返すときに使われるが、任意のReadableStreamを使えるため、好きなパスに対してレスポンスを返せるはず。それを応用しているのだと思われる。
あとは、このアイデアを元に、どうにかしてService WorkerまでReadableStreamを渡す技術が必要になるはず。
Service WorkerにpostMessage()するためには同じオリジンからするべきなはずなので、mitm.htmlが必要なのだろう。
(対応ソース)
あらゆるサイトからmitm.htmlにアクセスするために、iframeを使ってmitm.htmlを埋め込んでいるのだろう。
以下の対応ソースでiframePostMessage()が呼ばれると、iframeを作ってないときはmakeIframe(url)が呼ばれdocument.body.appendChild(iframe)される。そのため何度もstreamSaver.createWriteStream()されてもiframeがappendChildされるのは1回だけになっているのだろう。
(対応ソース)
このmitm.htmlを埋め込むプログラムがパッケージの"index"であり、importやrequire("...")すると使える。
流れのまとめ
StreamSaver.jsがimportやrequireされる
mitm.htmlが<iframe>で埋め込まれる
mitm.htmlにMessageChannelを使ってReadableStreamを送る
mitm.htmlがsw.jsにReadableStreamを送る
Service Workerのsw.jsがrespondWinth(送られたReadableStream)でダウンロードできるようになる。