🧑‍🔧

CloudFlareのWorkerで.tar.gz圧縮されたファイルの展開をする

2023/04/22に公開

Cloudflare Workerを使用して、.tar.gz形式で圧縮されたファイルを展開し、指定されたファイル名に一致するファイルを取得する方法です。tar形式の展開にモジュールを使用しようとするWorker内の依存関係で動きませんでした。そのため、.tar形式の展開を自前で実装した方法をここに残しておきます。

コードは次のレポジトリに格納されています。
https://github.com/takoyaki-3/cloudflare-worker-.tar.gz

コード概要

  1. URLから.tar.gz形式のファイルをダウンロード
  2. pakoライブラリを使用して、Gzip形式で圧縮されたデータを解凍
  3. Tar形式のバイナリデータを解析して、ファイル名とデータを取得
  4. 指定されたファイル名に一致するファイルを出力

コードの詳細

まず、handleRequest関数でリクエストパラメータからファイル名を取得します。次に、getTarBinary関数で.tar.gzファイルを取得し、ungzip関数を使って解凍します。gzipの展開にはpakoモジュールを利用し、tarの展開には自前で実装した関数を利用しています。

最後に、取得したファイル名とデータを出力します。リクエストパラメータで指定されたファイル名が一致する場合、そのファイル名とデータをコンソールに出力し、ファイルを出力します。以下にコードの全体像を載せます。

import { ungzip } from 'pako';

addEventListener('fetch', (event) => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  const urlParams = new URL(request.url).searchParams;
  const targetFileName = urlParams.get('file'); // リクエストパラメータからファイル名を取得

  const tarBinary = await getTarBinary(request)
  const files = await untar(tarBinary)

  let text = 'Tar files extracted. but [' + targetFileName + '] not found.';

  // ファイル名とデータを出力
  files.forEach(file => {
    if (file.name === targetFileName) { // リクエストパラメータで指定されたファイル名と一致する場合
      console.log(`File: ${file.name}`)
      console.log(`Data: ${file.data}`)

      text = (new TextDecoder).decode(new Uint8Array(file.data))
    }
  })

  return new Response(text, {
    headers: { 'content-type': 'text/plain' },
  })
}

async function getTarBinary(request) {
  const url = 'http://example.com/test.tar.gz';
  const response = await fetch(url)

  const decompressedData = ungzip(new Uint8Array(await response.arrayBuffer()));

  return decompressedData;
}

async function untar(tarBinary) {
  const CHUNK_SIZE = 512
  const files = []

  let position = 0

  while (position < tarBinary.length) {
    const headerChunk = tarBinary.slice(position, position + CHUNK_SIZE)
    const fileName = new TextDecoder().decode(headerChunk.slice(0, 100)).replace(/\0/g, '')

    if (fileName === '') {
      break
    }

    const fileSize = parseInt(new TextDecoder().decode(headerChunk.slice(124, 124 + 11)).replace(/\0/g, ''), 8)
    const dataStart = position + CHUNK_SIZE
    const dataEnd = dataStart + fileSize

    const fileData = tarBinary.slice(dataStart, dataEnd)

    files.push({ name: fileName, data: fileData })

    position = dataEnd + (CHUNK_SIZE - (fileSize % CHUNK_SIZE)) % CHUNK_SIZE
  }

  return files
}

まとめ

Cloudflare Workerを使って.tar.gz形式で圧縮されたファイルを展開し、指定されたファイル名に一致するファイルを取得する方法をまとめました。gzip形式の展開を自前で実装すると膨大なコード量となりそうなので、対応してないのがtarの方でよかったです。

Discussion