딥 애틀랜틱 스토리지: Web Workers에서 파일을 읽고 업로드

18339 단어 webworkerjavascript

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


7월 4일 방학에 나는 매우 심심해서 이상한 웹 페이지를 만들었다. Deep Atlantic Storage.
그것은 대서양 깊숙한 곳에 파일을 올릴 수 있는 무료 파일 저장 서비스로 묘사되어 있으며, 크기 제한과 내용 제한이 없다.
그것은 어떻게 일을 하는지, 내가 또 어떻게 감당할 수 있겠는가?
본고는 세 부분으로 구성된 시리즈의 두 번째 부분으로 이 시리즈는 심대서양 저장소의 배후 비밀을 밝히고 있다.
previous partUint8Array의 모든 위치를 정렬하는 알고리즘을 도입했다.
이제 나는 여기서 계속해서 웹 페이지가 파일 업로드를 어떻게 받아들이고 처리하는지 설명할 것이다.

파일 업로드


파일 업로드는 항상 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이 될 수 있습니다.
이것은 매우 간단하지만 대서양 깊은 곳의 저장에 있어서는 결코 이상적이지 않다.
이전 글에서 설명한 바와 같이 파일이 아무리 크더라도 모든 위치를 정렬한 결과는 두 가지 숫자로 표시할 수 있다. 파일에 얼마나 많은 0위와 1위인지.
전체 파일을 서버에 보낼 필요가 없습니다.반대로 브라우저에서는 계수가 훨씬 빨라진다.

파일 및 스펙클


2021년이 다가오면 자바스크립트는 무엇이든 할 수 있다.
JavaScript에서 <input type="file"> 요소에 대응하는 DOM 대상을 지정하면 .files[0] 속성을 통해 선택한 파일에 접근할 수 있습니다.
Using files from web applications은 이러한 API에 대해 더 자세히 설명합니다..files[0]File의 대상을 되돌려주고 이 대상은 Blob의 하위류이다.
그리고 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).

가독성 흐름

file.arrayBuffer()은 작동할 수 있지만 문제가 하나 있다. 만약에 사용자가 큰 파일을 선택했다면 전체 파일은 한 번에 모두 메모리에 읽어야 하기 때문에 상당한 메모리 압력을 초래한다.
이 문제를 해결하기 위해서, 나는 Streams API을 사용하여 비교적 작은 블록으로 파일을 읽고, 다음 블록을 읽기 전에 모든 블록을 처리할 수 있다.Blob 대상(예를 들어 위 코드 세그먼트의 file)에서 나는 .stream().getReader() 을 호출하여 ReadableStreamDefaultReader을 만들 수 있다.
그리고 reader.read()을 반복해서 호출할 수 있습니다. 이것은 데이터 블록이나 파일 종료 (EOF) 지시로 해석되는 약속을 되돌려줍니다.
파일을 블록별로 처리하고 1비트를 계산하는 전략은 다음과 같습니다.
  • 은 순환에서 reader.read()을 호출하여 다음 블록을 가져옵니다.
  • 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 Workers은 웹 콘텐츠를 백엔드 라인에서 스크립트를 실행하는 간단한 방법이다.
    Deep Atlantic Storage에서 나는 파일의 정렬이나 계수 작업을 인터넷 작업자에게 의뢰했다.

    사용자가 파일을 선택하고 폼을 제출할 때, 폼 이벤트 처리 프로그램은 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);
    });
    
    worker.js은 백그라운드에서 작동합니다.
    이것은 전역 변수 File에 분배된 함수에서 메시지를 수신한다(MessageEventonmessage 대상을 포함한다).
    그 다음에 이 함수는 countBitsBlob을 호출하여 파일에 몇 개의 0과 1이 있는지 계산한 다음에 전역 postMessage 함수를 호출하여 결과를 웹 페이지의 메인 라인으로 전달한다.
    그것 또한 던질 수 있는 모든 오류를 포착하여 주 라인에 전달한다.
    나는 이 두 가지 메시지에 type: "result"type: "error"을 포함하여 주 라인이 그것들을 구분할 수 있도록 했다.
    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 typespostMessage을 통과할 수 있지만 Error은 그 중의 하나가 아니기 때문이다.
    주 라인으로 돌아가서 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;
        }
      }
    }
    
    게다가 일부 사용자 인터페이스 마법(본문에는 설명이 없지만 웹 소스 코드를 볼 수 있음)을 더하면 Deep Atlantic Storage 웹 페이지를 구성할 수 있다.

    요약


    본고는 Deep Atlantic Storage의 배후 비밀을 밝히는 세 부분 시리즈의 두 번째 부분이다.
    previous article에서 디자인한 비트계수 알고리즘을 토대로 저는 이를 웹 응용 프로그램으로 전환시켜Streams API를 통해 업로드된 파일을 블록별로 읽고 웹워커스를 통해 복잡한 작업을 백엔드 라인으로 옮깁니다.
    이 시리즈의 next part은 비트 계수에 따라 파일을 재구성하기 위해 서버를 만드는 방법을 설명할 것입니다.

    좋은 웹페이지 즐겨찾기