背景
axiosでS3からzipをダウンロードして、それをダウンロードさせる場面があったので、良くある感じでコードを書いてみた。
前提として
- S3にはバックエンドから発行されるPresigned URLを使う
- バックエンドを通さず、そのURLに直接アクセスする
最初に書いたコード
await { data } = axios.get('....')
const blob = new Blob([data], { type: 'application/zip' })
const uri = URL.createObjectURL(blob)
const link = document.createElement('a')
link.download = 'sample.zip'
link.href = uri
link.click()
その結果…
ファイルのダウンロードは成功するけれど、解凍するとZipの中身が壊れている…
怪しいと思い、拡張子を .txt
にして開いてみると、バイナリのようなテキストが並んでるので、Zipとして正しく解釈できていない模様。
フロントエンドだけで完結する対策として、この2つを思いついたので、どっちが良いか試してみる
- S3 Presigned URL に直接アクセスさせる
- ファイルの変換をうまくやる
動作確認
1. S3 Presigned URLに直接アクセスさせる
const link = document.createElement('a')
link.download = 'sample.zip'
link.href = '....'
link.click()
とりあえずダウンロードはできる。けれど、ファイル名が download
で指定したものとは違う…
ググった結果ここにたどり着く。
This attribute only works for same-origin URLs.
という訳で、直接S3 Presigned URLにアクセスするとファイル名を制御できない。
セキュリティ考えるとまあそうかなという仕様ですね。
2. ファイルの変換をうまくやる
ArrayBufferを指定して、明示的にバイナリデータを受信するように設定、
const { data } = await axios.get('....', {
responseType: 'arraybuffer',
headers: { Accept: 'application/zip' },
})
const blob = new Blob([data], { type: 'application/zip' })
または、直接Blobを指定して構築してもらう。
const { blob: data } = await axios.get('....', {
responseType: 'blob',
headers: { Accept: 'application/zip' },
})
これであとは、ファイルへのリンクをクリックさせるだけ!
const uri = URL.createObjectURL(blob)
const link = document.createElement('a')
link.download = 'sample.zip'
link.href = uri
link.click()
これで無事、Zipファイルがダウンロードできました。