[教學] Azure Blob Storage 使用指南 – 大型檔案上傳篇

前一篇文章中說到可以使用 restful API 讓前端網頁程式直接對 Azure Blob Storage 進行檔案上傳,不過 Blob Storage 的 restful API 在 put method 時有限制檔案大小必須小於 256 MB,本篇將教學如果遇到檔案大小超過256 MB 的時候需要怎麼處理。

如果使用一般的 put method 上傳檔案超過 256 MB 會收到類似下面的 response

<?xml version="1.0" encoding="utf-8"?>
<Error>
    <Code>RequestBodyTooLarge</Code>
    <Message>The request body is too large and exceeds the maximum permissible limit.
RequestId:184d0d5a-d01a-011b-11e8-21131f000000
Time:2020-05-04T13:50:39.8719457Z</Message>
    <MaxLimit>268435456</MaxLimit>
</Error>

為了解決這個問題,上傳大於 256 GB 的檔案,就要分多個步驟處理

  1. 需要將檔案切成多份 (每份小於 256 GB),並且將每份都使用 Put Block 的 API 分批上傳

  2. 透過 Put Block List 將所有已上傳的 block 組在一起

一、將檔案切成多份,並且將每份都使用 Put Block 的 API 分批上傳

Put Block 就是將原本含有 sas 的 URL 後面加上 comp (值為 block) 與 blockid 這兩個參數

其中 blockid 是自己定義的值,但是有以下兩個限制:

  • 格式要符合 Base64,並且長度限制要小於等於64

  • 每個 blockid 之間的長度要相同

舉例原本含有 sas 的 url 是:

https://myaccount.blob.core.windows.net/container1/5e4ba47b?sv=2017-11-09&sr=b&se=2020-02-18T09:46:51Z&sp=rcwd&sig=KhqKo%2FP%2FhLarhZ3RAHGL4fvKF5iu5rHjEC1OUc1MKqw%3D

那這假如要是傳的檔案被切成了兩份,分別建立了兩個隨機的 block id 為 MTAwMA==MTAwMQ==

則兩個上傳的URL就會是

https://myaccount.blob.core.windows.net/container1/5e4ba47b?sv=2017-11-09&sr=b&se=2020-02-18T09:46:51Z&sp=rcwd&sig=KhqKo%2FP%2FhLarhZ3RAHGL4fvKF5iu5rHjEC1OUc1MKqw%3D&comp=block&blockid=MTAwMA==
https://myaccount.blob.core.windows.net/container1/5e4ba47b?sv=2017-11-09&sr=b&se=2020-02-18T09:46:51Z&sp=rcwd&sig=KhqKo%2FP%2FhLarhZ3RAHGL4fvKF5iu5rHjEC1OUc1MKqw%3D&comp=block&blockid=MTAwMQ==

對這兩個 url 使用 put method 上傳檔案

二、透過 Put Block List 將所有已上傳的 block 組在一起

當每個 block 都上傳完畢後,再透過 Put Block List 的 API 將這些 block 組合在一起更新進去,Put Block List 的 url 是含有 sas 的 blob url 後面加上 comp (值為blocklist) 這個參數

以上面的 sas url 為例,那 url 便是:

https://myaccount.blob.core.windows.net/container1/5e4ba47b?sv=2017-11-09&sr=b&se=2020-02-18T09:46:51Z&sp=rcwd&sig=KhqKo%2FP%2FhLarhZ3RAHGL4fvKF5iu5rHjEC1OUc1MKqw%3D&comp=blocklist

而 Put Block List 內容則是要描述使用哪些 block 來進行組合 (格式為 xml),以剛剛兩個 block id 為例,那內容就會是:

<?xml version="1.0" encoding="utf-8"?>
<BlockList>
    <Latest>MTAwMA==</Latest>
    <Latest>MTAwMQ==</Latest>
</BlockList>

javascript 程式碼範例:

let blobUrl = getBlobUrl();
let file = document.querySelector('#upload-file').files[0];
let fileReader = new FileReader();
fileReader.onload = async function() {
  let blockStart = 0;
  let blockSize = 52428800; // 每份 block 大小 (50MB)
  let blockIds = []; // 要更新的 block id
  while (blockStart < fileReader.result.byteLength) {
    let blockId = btoa(1000 + blockStart / blockSize);
    await axios.put(blobUrl + "&comp=block&blockid=" + blockId, fileReader.result.slice(blockStart, blockStart + blockSize), {
      headers: {
        'x-ms-blob-type': 'BlockBlob'
      }
    });
    blockIds.push(blockId);
    blockStart += blockSize;
  }

  if (blockIds.length > 0) {
    let content = '<?xml version="1.0" encoding="utf-8"?><BlockList><Latest>';
    content += blockIds.join('</Latest><Latest>');
    content += '</Latest></BlockList>';

    await axios.put(blobUrl + "&comp=blocklist", content);

    console.log('上傳完畢');
  }
}
fileReader.readAsArrayBuffer(file);

發表迴響