ブラウザからAzureBlobStorageにファイルをアップロードしてみた

この記事は、 KENTEM TechBlog アドベントカレンダー2023 14日目、12月20日の記事です。

こんにちは、KENTEM のまつです。
ブラウザからAzureBlobStorageにファイルをアップロードする機会があり、azure-sdk-for-jsstorage-blobを使用しました。
Microsoft LearnにHowToは揃っていますが、ブラウザからアップロードする記事が少なかったため、いくつかポイントを紹介します。

Azure Blob Storageとは?

Microsoftが提供する大量の非構造化データ(※1)を格納するために最適化されたクラウド用オブジェクトストレージです。
HTTP/HTTPS経由でBlobStorage内のオブジェクトにアクセスが可能で、APIやクライアントライブラリが用意されています。
Azureのサービスなので、データのバックアップや冗長構成などをオプションから選択するだけで実現可能です。
また、コストが非常に低い(※2)ことも特徴として挙げられます。
このサービスを利用することで、可用性、耐久性に優れたストレージを低コストで提供することができます。

※1 特定のデータ モデルや定義に従っていないデータのことを非構造化データと呼びます。
※2 2023/12/11現在、最初の50テラバイト: 約3円/GB

なぜブラウザから?

ブラウザからファイルにアクセスできることで、クロスプラットフォームな対応ができると考えました。

【ポイント①】認証

ブラウザからの認証にはazure/identityのDefaultAzureCreadentialを使用することができません。

DefaultAzureCredential is not supported in the browser
ブラウザからの認証にはいくつかの手段がありますが、SASを用いた認証が一番簡単です。
AccountService、BlobContainer、BlobのいずれかでSASを発行して認証します。

AccountServiceのSASを発行する例:
ブラウザでは不特定多数にコードが配布されるリスクがあります。
一例としてあげますが、SASのようなセキュリティに関わる文字列をソースコード中に埋め込むことは推奨しません。

import AzureStorageBlob = require('@azure/storage-blob');

const ServiceSAS = '<YOUR_SERVICE_SAS>';
const ServiceUrl = '<YOUR_SERVICE_URL>';
const BlobContainerName = '<YOUR_CONTAINER_NAME>';
async function Upload(file: File): Promise<void> {
    const serviceClient = new AzureStorageBlob.BlobServiceClient(`${ServiceUrl}${ServiceSAS}`);
    const container = serviceClient.getContainerClient(BlobContainerName);
    const client = container.getBlockBlobClient(file.name);
    await client.uploadData(file);
}

BlobのSASを発行する例:
API上でSASを発行してBlobリソースへのURIを返却することで、BlockBlobClientを作成する際に認証します。

async function Upload(file: File): Promise<void> {
    const params: RequestInit = {
        method: "GET"
    };
    const response = await fetch(`/api/azure/getbloburl?blobName=${file.name}`, params);
    const blobUrl = await response.text();

    const client = new AzureStorageBlob.BlockBlobClient(blobUrl);
    await client.uploadData(file);
}

【ポイント②】CORS設定

ブラウザからAzureBlobStorageにファイルをアップロードする際、内部的にAPIを使用してアップロードする関係上、CORS(クロスオリジンリソース共有)の設定をしなければ、APIへのアクセスがブロックされます。

No 'Access-Control-Allow-Origin' header is present on the requested resource

AzurePortalから対象のAzureStorageAccountを開いて、「リソースの共有(CORS)」からOriginの設定を行います。

CORS設定

設定項目 設定値 備考
許可されたオリジン https://localhost:7196 APIを呼び出すサイトになります。ワイルドカード(*)が使用できます。
許可されたメソッド HEAD, GET, POST, PUT, DELETE, OPTIONS, MERGE 使用するHTTPメソッドを指定します。API Referenceを確認して、必要なメソッドを許可します。
許可されたヘッダー * 必要に応じて指定してください。
公開されるヘッダー *
最長有効期間 1800 プリフライトリクエストのキャッシュ時間を指定します。

【ポイント③】一度にアップロードできるサイズの上限

azure-sdk-for-jsのstorage-blobはアップロードするファイルサイズが大きい場合、ファイルを分割して送信をします。
例えば、BlockBlobClientではBlockBlobParallelUploadOptionsを指定することで、分割した場合のブロックサイズや分割せずに送る上限サイズを設定することが可能です。
よって、非常に大きなファイルを扱うこともできますが、アップロードの仕方や使用するAPIのバージョンによってサイズの上限が決まっています。
各バージョン毎のアップロード上限一覧

アップロード上限を超えるとHttpStatusCode: 413が返却されて、アップロードが失敗します。

net::ERROR_FAILED 413 (Payload Too Large)
サーバーに要求するAPIのバージョン(QueryStringのパラメータsvで設定)や応答されるバージョン(応答ヘッダーのX-Ms-Version)によっても変わりますが、上記エラーが発生した場合、以下のように一度にアップロードされるサイズの上限を設定する必要があります。

import AzureStorageBlob = require('@azure/storage-blob');

async function Upload(file: File, blobUrl: string): Promise<void> {
    const client = new AzureStorageBlob.BlockBlobClient(blobUrl);
    const options: AzureStorageBlob.BlockBlobParallelUploadOptions = {
        maxSingleShotSize: 4 * 1024 * 1024    // 4MBを越えるファイルは分割される
    };
    await client.uploadData(file, options);
}

ご紹介させていただいたように、ブラウザからのアップロードはサーバーサイドのNode.jsやその他クライアントとは違った制限がありますので、少し設定が必要になります。
ブラウザからアップロードができるようになるとクラウドストレージサービスやEDIサービスなどに留まらず、様々なサービスに利用できそうです。
この記事がみなさまのAzureライフの一助になればと思います。

KENTEMでは、様々な拠点でエンジニアを大募集しています!
建設×ITにご興味頂いた方は、是非下記のリンクからご応募ください。
https://hrmos.co/pages/kentem2211/jobs/A0008hrmos.co hrmos.co hrmos.co hrmos.co hrmos.co hrmos.co hrmos.co hrmos.co