14
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-01-27

概要

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がない)のが、うーむ...という感じ。
用意されていないのは、分割してリクエストを送っている分、うまいこと制御するのが難しかったりするのかも。

参考情報

14
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?