Azure Storage の SAS URL を使って SPA から直接 Blob ファイルをアップロードする

## 目的

Web アプリケーションのあるある機能なのですが、管理者が静的コンテンツを管理画面からアップロードしてユーザーに配信したい場合があります。このようなニーズに対しては、Azure Storage や AWS S3 などのストレージサービスに対して Web アプリケーション経由でファイルをアップロードして、ユーザーに対しては Amazon CloudFront や Azure CDN などを組み込むのが一般的な手段かと思います。

この記事では静的コンテンツをWeb アプリケーションのサーバーサイドで処理せず、直接 Azure Blob Storage にアップロードして、Web アプリケーションのランニングコストがほとんどかからない SPA で実現しようというものです。

:x:　この方法でなく：管理者 ⇒ ブラウザ ⇒ Webサーバー ⇒ Azure Blob Storage ⇒ CDN ⇒ ユーザー
:o:　この方法で実現：管理者 ⇒ ブラウザ ⇒ Azure Blob Storage ⇒ CDN ⇒ ユーザー

## 本記事の要約

- Azure Storage に CORS の設定をします
- Azure Storage の Blob コンテナーの SAS URL を発行します
- Azure Blob Storage Client Library (@azure/storage-blob) を使って SPA をつくります
- ブラウザから直接 Azure Blob Storage にアップロードできます

参考記事 [クイック スタート:ブラウザーで JavaScript v12 SDK を使用して BLOB を管理する](https://docs.microsoft.com/ja-jp/azure/storage/blobs/quickstart-blobs-javascript-browser) 。

## CORS（Cross-Origin Resource Sharing）について

今回の方法では、ブラウザから Azure Blob Storage に対して直接 Web API をリクエストしますので、SPA を配信するオリジンとは異なるオリジンにアクセスが行われます。この設定をしておかないと、リクエストが失敗してしまいます。

詳細は [オリジン間リソース共有 (CORS)](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS) を参照


## Azure Storage の SAS URL について

Azure Storage の Blob、Queue、Table、File といった各種サービスに対して、アクセス許可を与えることができる仕組みです。
SAS URL は「有効期限」と「IPアドレス制限」が可能で、プログラムによって動的に発行することもできるのでセキュリティ面も安心です。

詳細は [Shared Access Signatures (SAS) を使用して Azure Storage リソースへの制限付きアクセスを許可する](https://docs.microsoft.com/ja-jp/azure/storage/common/storage-sas-overview) を参照

## Azure Storage の CORS を設定する

まずは Azure Portal でストレージアカウントを作成します。
ストレージアカウントを作成したあと[設定]...[リソースの共有 (CORS)]のメニューにアクセスします。
![CORS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2000953/b48e4adc-c3b0-c9b7-9a58-a54d6f8b3a1b.png)
ここでは [Blob service] のタブを選択して SPA が配置されるオリジンの情報を設定します。アスタリスク（*）を設定した項目は任意の値を許可することになりますので、本番環境においてはしかるべき値を設定しましょう。

## Azure Storage の Blob コンテナーの SAS URL を発行する

Azure Blob Storage では SAS URL は以下の単位で発行することができます。

- コンテナー
- Blob
- Blob バージョン
- Blob スナップショット
- ディレクトリ（階層名前空間が有効になっているストレージアカウントのみ）

コンテナーに対して SAS URL を発行する場合は、該当コンテナーの外側（別のコンテナーや別のコンテナー内のBlob）にはアクセスできないので、操作できる範囲を限定しやすくわかりやすいので、ここではコンテナーに対して SAS URL を発行します。

コンテナーを作成したあと[設定]...[共有アクセス トークン]のメニューにアクセスします。

![SAS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2000953/83c1bda8-e9f2-d279-9d66-5ac0044145e8.png)

[アクセス許可]の入力項目では、必要なアクセス許可を設定します。（ひとまず全部チェックします）
[有効期限]と[使用できるIPアドレス]を設定します。IPアドレスは、自分のPCのグローバルIPを調べて設定してください。
[SAS トークンおよび URL を生成]ボタンをクリックすることで、BLOB SAS URL が発行されます。

## Vue CLI で SPA アプリケーションを作成

Vue CLI で 新しい SPA アプリケーションをつくります。
Node.js および Vue CLI がインストールされていない場合はインストールしてください。
お好きなオプションで作っていただいて良いのですが、ここでは TypeScript を選択して以下のオプションで作成しました。

```
> vue create blob-sas

Vue CLI v4.5.13
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, TS, Router, Linter
? Choose a version of Vue.js that you want to start the project with 3.x
? Use class-style component syntax? No
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Prettier
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
```

Vue の準備ができたら以下のコマンドを実行します。

```
> cd blob-sas
> npm install --save @azure/storage-blob
```

Home.vueファイルにアップロード機能を追加していきます。
以下は、ファイルを入力したタイミングで Blob コンテナーにファイルをアップロードするコードです。
BlobServiceClient のコンストラクタで SAS URL を指定していますので、発行した SAS URL に置き換えてください。

```vue:Home.vue
<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
    <input ref="fileUploadInputRef" type="file" name="file" id="fileUploadInput" @change="onChangeFileUploadInput" multiple />
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src
import { BlobServiceClient, ContainerClient } from '@azure/storage-blob';

export default defineComponent({
  name: "Home",
  components: {
    HelloWorld,
  },
  setup() {
    const fileUploadInputRef = ref<HTMLInputElement>();
    const onChangeFileUploadInput = async () => {
      if (!fileUploadInputRef.value) {
        return;
      }
      if (!fileUploadInputRef.value.files) {
        return;
      }
      const blobServiceClient = new BlobServiceClient('https://XXXXXXXXXX.blob.core.windows.net/XXXX?sp=racwdl&st=2021-10-01T00:00:00Z&se=2021-10-31T23:59:59Z&spr=https&sv=2020-08-04&sr=c&sig=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
      const containerName = '.';
      const containerClient = blobServiceClient.getContainerClient(containerName);

      try {
        const promises = [];
        for (const file of fileUploadInputRef.value.files) {
          const blockBlobClient = containerClient.getBlockBlobClient(file.name);
          promises.push(
            blockBlobClient.uploadData(file)
          );
        }
        await Promise.all(promises);
      } catch (error) {
        console.log(error.message);
      }
      fileUploadInputRef.value.value = '';
    };

    return {
      fileUploadInputRef,
      onChangeFileUploadInput,
    };

  }
});
</script>
```

それでは実行しましょう。

```
> npm run serve
```

![APP.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2000953/ddf0f461-2080-bd6f-9dd9-28862633b074.png)

[ファイルを選択]ボタンをクリックして、無事にアップロードできていることを確認できました。

![BLOB.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2000953/eb1e3b70-1300-a9b6-092f-5cca807262eb.png)

## 他にも機能を追加する

ファイルをアップロードするだけではなく、いろいろと機能を追加したエクスプローラー風のアプリケーションをつくってみました。

- ツリー構造で閲覧できる
- アップロード中にプログレスバーを表示する
- フォルダごとアップロードする
- ZIPファイルを解凍してアップロードする

![capture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2000953/04e58974-b644-b432-4551-03a14e12f17d.png)

GitHub に上げてありますので参考にしてください。ソースコードは[こちら](https://github.com/watertrans/AzureBlobFiler)です。
SPA なので GitHub Pages にも上げてあります。デモアプリケーションは[こちら](https://watertrans.github.io/AzureBlobFiler/)です。（CORS 設定が済ませてあれば SAS URL を入力して実行可能です）

## 注意事項

- IE での利用は想定していません。
- SAS URL はアップロード可能なファイルのサイズ制限をかけることができません。
- SAS URL の取り扱いには注意しましょう。GitHub にうっかり SAS URL をコミットしてしまうと自由にファイルを置かれてしまいます。
- SAS URL は発行時の署名につかったアクセスキーを交換（再発行）することで無効化できます。