ディープアトランティックストレージWebブラウザでのファイルアップロードの読み込み


This post is originally published on yoursunny.com blog https://yoursunny.com/t/2021/das-file-worker/


私は7月4日の休暇で退屈しているので、私は気まぐれなウェブページを作りました:Deep Atlantic Storage .
それは無料のファイルストレージサービスとして記述され、そこでは、任意のファイルをアップロードすることができます大西洋に深く、サイズ制限とコンテンツの制限なしで格納されます.
どのように、それはどのように私はそれを提供する余裕がありますか?
この記事は、大西洋のストレージの背後にある秘密を明らかに3部シリーズの2番目です.
The previous part すべてのビットをAにソートするアルゴリズムを導入しましたUint8Array .
現在、私はそこから続きます、そして、ウェブページがどのようにファイルアップロードを受け入れて、処理するかについて説明してください.

ファイルアップロード


私が覚えている限り、ファイルアップロードは常にHTML標準の一部でした.
<form action="upload.php" method="POST" enctype="multipart/form-data">
  <input type="file" name="file">
  <input type="submit" value="upload">
</form>
これは、ユーザーがローカルファイルを選択できるようにするボタンを作成します.
フォームが送信されると、ファイル名と内容がサーバーに送信され、サーバー側スクリプトがprocess the upload .
それは簡単ですが、深い大西洋のストレージに最適ではありません.
最後の記事で説明したように、ファイルがどれくらい大きいかに関係なく、すべてのビットをソートする結果は、ちょうど2つの数0 ビットと1 ビットがファイルにあります.
ファイル全体をサーバに送る必要はありません代わりに、ブラウザでカウントをはるかに高速になります.

ファイルとblob


2021年まで早送り、JavaScriptはすべてを行うことができます.
JavaScriptでは、<input type="file"> 要素は、(最初)選択したファイルにアクセスすることができます.files[0] プロパティ.
Using files from web applications これらのAPIの更なる説明があります..files[0] aを返すFile のサブクラスです.Blob .
Then, Blob.prototype.arrayBuffer() 関数はファイル全体をArrayBuffer , コンテンツへのアクセスを提供する.
<form id="demo_form">
<input id="demo_upload" type="file" required>
<input type="submit">
</form>
<script>
document.querySelector("#demo_form").addEventListener("submit", async (evt) => {
  evt.preventDefault();
  const file = document.querySelector("#demo_upload").files[0];
  console.log(`file size ${file.size} bytes`);
  const payload = new Uint8Array(await file.arrayBuffer());
  const [cnt0, cnt1] = countBits(payload); // from the previous article
  console.log(`file has ${cnt0} zeros and ${cnt1} ones`);
});
</script>
このコードはイベントリスナーを<form> .
フォームが送信されると、コールバック関数はファイルをArrayBuffer そして、Uint8Array ビットカウント機能countBits 前記事より)

readablestream

file.arrayBuffer() 作品が、問題がある:ユーザーが巨大なファイルを選択した場合、ファイル全体が一度にメモリに読み込まれる必要があります、かなりのメモリストレスを引き起こす.
この問題を解決するためにStreams API 小さいチャンクでファイルを読み込み、各チャンクを処理する前に次の文字を読み込みます.
からBlob オブジェクトfile 上記のスニペットでは、コールできます .stream().getReader() 作成するReadableStreamDefaultReader .
そして、私は繰り返し呼び出すことができますreader.read() , これは、データのチャンクかファイル終端(EOF)指示のどちらかに解決する見込みを返します.
チャンクによってファイルチャンクを処理し1 ビットがあります、私の戦略はそうです:
  • 呼び出しreader.read() ループで、次のチャンクを取得します.
  • If done が真で、EOFが到達したことを示すと、ループを中断します.
  • 数を加える1 チャンクの各バイトのビットは、全体のカウンタになります.
  • 最後に、どのように多くの計算0 ビットはファイルサイズから、アクセス可能ですblob.size プロパティ.
  • async function countBitsBlob(blob: Blob): Promise<[cnt0: number, cnt1: number]> {
      const reader = (blob.stream() as ReadableStream<Uint8Array>).getReader();
      let cnt = 0;
      while (true) {
        const { done, value: chunk } = await reader.read();
        if (done) {
          break;
        }
        for (const b of chunk!) {
          cnt += ONES[b];
        }
      }
      return [8 * blob.size - cnt, cnt];
    }
    

    Webワーカー


    Webアプリケーションでは、バックグラウンドスレッドで複雑な計算を実行するのが最善です.そのため、主スレッドがユーザー間の対話に迅速に対応できます.
    Web Workers Webコンテンツの背景スレッドでスクリプトを実行するための簡単な手段です.
    深い大西洋保管では、私はファイルのビットをソートするか、数える仕事をウェブ労働者に委任しました.

    ユーザーがファイルを選択し、フォームを送信すると、フォームイベントハンドラーはWorker (もしそうしなければ)Worker.prototype.postMessage() 通るFile バックグラウンドスレッドへのオブジェクト.
    let worker;
    document.querySelector("#demo_form").addEventListener("submit", async (evt) => {
      evt.preventDefault();
      const file = document.querySelector("#demo_upload").files[0];
      worker ??= new Worker("worker.js");
      worker.onmessage = handleWorkerMessage; // described later
      worker.postMessage(file);
    });
    
    The worker.js バックグラウンドで実行します.
    メッセージを受信するMessageEvent エンクロージャFile グローバルに割り当てられた関数onmessage 変数.
    この関数はcountBitsBlob ゼロとファイルの数をカウントするにはpostMessage 関数は、結果をWebページのメインスレッドに渡します.
    また、スローされた可能性のあるエラーをキャッチし、メインスレッドにも渡す.
    含まれているtype: "result" and type: "error" これらの2種類のメッセージでは、メインスレッドがそれらを区別できるようにします.
    onmessage = async (evt) => {
      const file = evt.data;
      try {
        const result = await countBitsBlob(file);
        postMessage({ type: "result", result });
      } catch (err) {
        postMessage({ type: "error", error: `${err}` });
      }
    };
    
    注意してくださいcatch 条項Error オブジェクトは、postMessage .
    これは必要なのはa handful of types 通ることができるpostMessage , でもError それらの1つではない.
    メインスレッドに戻るhandleWorkerMessage に割り当てられたworker.onmessage プロパティは、ワーカースレッドからメッセージを受け取ります.
    function handleWorkerMessage(evt) {
      const response = evt.data;
      switch (response.type) {
        case "result": {
          const [cnt0, cnt1] = response.result;
          console.log(`file has ${cnt0} zeros and ${cnt1} ones`);
          break;
        }
        case "error": {
          console.error("worker error", response.error);
          break;
        }
      }
    }
    
    いくつかのユーザーインターフェイスの魔法(この記事では説明されていないが、Webページのソースコードを見ることができます)と組み合わせることで、これはディープ大西洋ストレージWebページを構成します.

    概要


    この記事は、後ろの秘密を明らかにする3部シリーズの第2ですDeep Atlantic Storage .
    ビットカウントアルゴリズムの設計previous article , 私はストリームAPI経由でチャンクでアップロードされたファイルのチャンクを読み取り、Webワーカーを介してバックグラウンドスレッドに重いリフティングを移動するWebアプリケーションにそれを回した.
    The next part このシリーズでは、どのように私はビットカウントからファイルを再構築するサーバーを作った説明します.