ReadableStreamをストリーミングしながらダウンロードできるStreamSaver.jsの仕組みを読み解きたい
はじめに
残念ながら、ブラウザに提供されている標準に機能にはなさそう。
でも、どうやってブラウザにある機能だけで実装しているかがとても気になったので、ソースコードから仕組みを読み解きたくなった。
なぜストリーミングしながらのダウンロードをしたいか?
1GBとか1TBとかのファイルだったらメモリに置くのは、好ましくないかつ現実的ではないので、ストリーミングしながらダウンロードしたい。
ブラウザ標準でこの機能が実装されることが一番嬉しい。Streams APIかFile APIにも盛り込まれてほしい。
小さいデータなら、全部読み切ってBlobにしてBlob URLを生成してopenとかすればダウンロードはできる。
使われている技術
最終的にService WorkerのrespondWith()でダウンロードされる。
MessageChannelはiframe内のJSにReadableStreamを渡すために使われる。
どのソース
パッケージ外のリソース
完結してないの意味は、以下のコードのソースはリポジトリ上にあるがポータブルではないという意味(例えば、GitHub Pagesがダウンすると使えなくなるはず)。
以下の作者さんのGitHub PagesがHTTPSになることを利用している様子。
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)でダウンロードできるようになる。