JavaScript
Azure
blob
upload
AzureStorage

Azure Storage、マルチパートでアップロードできないってよ

概要

Azure Storage(Blob)へファイルをアップロードする際は、マルチパートが使えない模様。困った。

Blob APIを使う必要があるらしいがPUTの制限があるため、
256MB以上のファイルはブロックに分割してアップロードする必要がある模様。

しかし自分で実装するとなると、これがそこそこ面倒。困った。

そこで、ブラウザから(JavaScriptで)分割してアップロードする方法を調べていたところ、
公式のライブラリがあるようなので、実際に試してみた。

結論から言うと、ライブラリを利用することで、分割アップロードを簡単に実現することができた。
Azure Storage(Blob)へのファイルアップロードはファイルサイズに関係なく、
今回試した方法を使うで良さそう。

※ 試したコードはgithubでも公開中 azure-storage-file-uploader

デモ

azure-storage-upload-download.gif
※ 256MB以上のファイルをアップロードすると、アップロード時間が長いので1MBのファイルでのデモ

手順

Azure Storageの準備

 1. ストレージアカウントの作成
 2. CORSの設定
 3. SASトークンの生成

1. ストレージアカウントの作成

公式の手順を参考に作成するだけ
※ ストレージの種類は今回のターゲットの「BLOB ストレージ」を選択

2. CORSの設定

サイドバーから「CORS」を選択し、以下のように設定。

許可されたオリジン: *
許可されたメソッド: GET,HEAD,OPTIONS,PUT,POST
許可されたヘッダー: Accept,Content-Type,Origin,x-ms-blob-content-disposition,x-ms-blob-content-md5,x-ms-blob-content-type,x-ms-blob-type,x-ms-client-request-id,x-ms-date,x-ms-version
公開されるヘッダー: *
最大期間(秒): 0

※ 許可されたヘッダーに関しては「*」ではなく調査しがてら明示的に必要なヘッダーを指定した。

3. SASトークンの生成

サイドバーから「Shared Access Signature」を選択。

お試し程度であれば、基本デフォルト値で問題ないが、
「開始日時と有効期限の日時」のタイムゾーンは「UTC +09:00」に変更することをお忘れなく。
最後に「SASの生成」を押下し、生成された「SAS トークン」をメモっておく。

これでAzure Storageの準備は完了。

ファイルアップロードの実装

 1. ライブラリの導入
 2. 必要な情報の定数化
 3. コンテナの生成
 4. アップロード
 5. プログレス表示
 6. ダウンロード

最終的なコードはこんな感じ。ライブラリのサンプルコードを参考にしつつ進めた。
せっかくなので、アップロードだけでなく、プログレスバーやダウンロードも入れてみた。

index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Azure Storage File Uploader</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
    </head>
    <body>
        <div><img id="image" src=""/></div>
        <input id="inputFile" type="file" />
        <i id="download" class="fa fa-download" aria-hidden="true"></i>
        <div><progress id="progress" max="100"></progress></div>
        <style>
         html, body {
             width: 100%;
             height: 100%;
             margin: 0;
             padding: 0;
         }
         html {
             display: table;
         }
         body {
             display: table-cell;
             text-align: center;
             vertical-align: middle;
         }
         #image{
             width: 300px;
         }
         #progress {
             display: none;
             margin-left: -70px;
         }
         #download {
             display: none;
             cursor: pointer;
         }
        </style>
        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
        <script src="azure-storage.common.min.js"></script>
        <script src="azure-storage.blob.min.js"></script>
        <script src="main.js"></script>
    </body>
</html>
main.js
const SAS_TOKEN = 'sas-token';
const CONTAINER_NAME = 'my-container-name';
const BLOB_URI = 'https://my-storage-name.blob.core.windows.net';

const blobService = AzureStorage.createBlobServiceWithSas(BLOB_URI, SAS_TOKEN);

$('#inputFile').on('change', (event) => {
    blobService.createContainerIfNotExists(CONTAINER_NAME, (error, result) => {
        if (error) {
            console.error('create container error');
            return;
        }

        const file = event.target.files[0];
        const customBlockSize = (file.size > 1024 * 1024 * 32)? (1024 * 1024 * 4) : (1024 * 512);
        blobService.singleBlobPutThresholdInBytes = customBlockSize;

        const options = {
            blockSize: customBlockSize,
            contentSettings: {
                contentDisposition: 'attachment'
            }
        };

        beforeUpload();

        let finishedOrError = false;
        const speedSummary = blobService.createBlockBlobFromBrowserFile(CONTAINER_NAME, file.name, file, options, (error, result, response) => {
            finishedOrError = true;
            if (error) {
                console.error('upload error');
                return;
            }
            console.log('upload successfully');
            afterUpload();
        });

        function refreshProgress() {
            setTimeout(() => {
                if (!finishedOrError) {
                    $('#progress').val(speedSummary.getCompletePercent());
                    refreshProgress();
                }
            }, 200);
        }

        refreshProgress();    
    });

});

$('#download').on('click', (event) => {
    const file = $('#inputFile').prop('files').item(0);
    if (file == null) return;
    const downloadUrl = blobService.getUrl(CONTAINER_NAME, file.name, SAS_TOKEN);
    $('#image').attr('src', downloadUrl);
});

function beforeUpload() {
    $('#progress').val(0);
    $('#progress').show();
    $('#download').hide();
}

function afterUpload() {
    $('#progress').hide();
    $('#download').show();
}

1. ライブラリの導入

READMEどおりに進めればOK。
生成された以下の2ファイルをindex.htmlで読み込む。

  • azure-storage.common.min.js
  • azure-storage.blob.min.js

2. 必要な情報の定数化

以下の3つがあればOK。
SASトークン: 生成したSASトークン  ※ "?"以降の値を使用する
コンテナ名: 任意のコンテナ名
Blob URI: https:{ストレージ名}.blob.core.windows.net

const SAS_TOKEN = 'sas-token';
const CONTAINER_NAME = 'my-container-name';
const BLOB_URI = 'https://my-storage-name.blob.core.windows.net';

3. コンテナの生成

任意のコンテナ名を引数とすることで、コンテナが生成される。
既に同名のコンテナが存在する場合は、再生成されることはないので、必ず実行して問題ない。

blobService.createContainerIfNotExists(CONTAINER_NAME, (error, result) => {

ドキュメントはこちら

4. アップロード

コンテナ名、ファイル情報、オプションを指定してファイルをアップロードする。
ファイルサイズに応じたブロックに分割して、リクエストが送信される。

const file = event.target.files[0];
const customBlockSize = (file.size > 1024 * 1024 * 32)? (1024 * 1024 * 4) : (1024 * 512); // ファイルサイズに応じてブロックサイズを決定
blobService.singleBlobPutThresholdInBytes = customBlockSize;

const options = {
    blockSize: customBlockSize,
    contentSettings: {
        contentDisposition: 'attachment' 
    }
};

beforeUpload();

let finishedOrError = false;

// プログレスなどが取得できる、オブジェクトが返却される
const speedSummary = blobService.createBlockBlobFromBrowserFile(CONTAINER_NAME, file.name, file, options, (error, result, response) => {
    finishedOrError = true;
    if (error) {
        console.error('upload error');
        return;
    }
    console.log('upload successfully');
    afterUpload();
});

ドキュメントはこちら

5. プログレス表示

アップロード時に返却される、speedSummaryオブジェクトからプログレス情報を取得可能なため、
一定時間毎にプログレスバーを更新することで実現可能。

// プログレスなどが取得できる、オブジェクトが返却される
const speedSummary = blobService.createBlockBlobFromBrowserFile(CONTAINER_NAME, file.name, file, options, (error, result, response) => {
    finishedOrError = true;
    if (error) {
        console.error('upload error');
        return;
    }
    console.log('upload successfully');
    afterUpload();
});

function refreshProgress() {
    setTimeout(() => {
        if (!finishedOrError) {
            $('#progress').val(speedSummary.getCompletePercent());
            refreshProgress();
        }
    }, 200);
}

refreshProgress();

6. ダウンロード

コンテナ名、ファイル名、SASトークンからダウンロードURLを生成可能。

const downloadUrl = blobService.getUrl(CONTAINER_NAME, file.name, SAS_TOKEN);
$('#image').attr('src', downloadUrl);

ドキュメントはこちら

あとがき

分割アップロードを自分で実装するとなると、そこそこ面倒なため、こういうライブラリがあるのは嬉しい。
ただ、アップロードのキャンセルができない(APIがない)のが、うーむ...という感じ。
用意されていないのは、分割してリクエストを送っている分、うまいこと制御するのが難しかったりするのかも。

参考情報