JavaでAzure Blob Storageを操作する（SASトークン、ファイル操作）

# この記事について

この記事では、[Azure Blob Storage](https://azure.microsoft.com/ja-jp/services/storage/blobs/)をJavaライブラリを使用して操作する際に必要なことを調べたのでメモしていきます。

使用しているライブラリなどは以下のバージョンです。

* Java version: OpenJDK Client VM AdoptOpenJDK (build 11.0.6+10, mixed mode)
* [Azure Storage Blob](https://mvnrepository.com/artifact/com.azure/azure-storage-blob): 12.5.0

## 準備するもの

### Azureのアカウント

当然のごとく必須です。

### Azure Storage Account

Azure Portalから、ダッシュボード > ストレージアカウント > ストレージ アカウントの作成 を選択し、
ストレージアカウント名などを入力し、アカウントの種類で「Blob Storage」を選択してください。

その他のパラメータは用途に応じて自由に決めます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280383/cc3f3fc1-e496-a0d7-94b0-2afe9d49db01.png)

今回は、**storage-account**という名前で作成したことを前提に進めます。
※実際には、ストレージアカウント名には半角英数しか使えないため、当然のようにエラーになります。

### コンテナの作成

Blob Storageでは、以下の単位でリソースが管理されています。

* ストレージ アカウント
* コンテナ
* BLOB（ファイル）

詳細については、[こちら](https://docs.microsoft.com/ja-jp/azure/storage/blobs/storage-blobs-introduction#blob-storage-resources)を参照してください。

今回は、**log**という名前で作成したことを前提に進めます。

### 接続文字列の取得

ストレージアカウントを取得すると、アクセスキーが取得できます。

ストレージアカウント > ストレージアカウント名 > アクセスキーにアクセスすると、アクセスキーが取得できます。

接続文字列は以下のような形式となっているようですので、取得するか、自分でいい感じに組み立ててください

* DefaultEndpointsProtocol = https
* AccountName = ストレージアカウント名
* AccountKey = 生成されたアカウントキー
* EndpointSuffix = core.windows.net （固定っぽい）

`DefaultEndpointsProtocol=${DefaultEndpointsProtocol};AccountName=${AccountName};AccountKey=${AccountKey};EndpointSuffix=core.windows.net`

## SASトークンを取得する

準備ができたので、実際にBlobストレージを操作してみましょう。

ますSASトークンの取得を行ってみます。

SASトークンについては以下の説明をご覧ください
https://docs.microsoft.com/ja-jp/azure/storage/common/storage-sas-overview

```java
// 取得した接続文字列を使用し、BlobClientを取得します
// BLOB_CONNECTION_SETINGは、Azure Portalで取得した接続文字列を使用してください.
var blobClient = new BlobServiceClientBuilder().connectionString(BLOB_CONNECTION_STRING).buildClient();

// SASに割り当てる権限などを設定します。
var permission = AccountSasPermission.parse("rwacd"); // read + write + add + create + deleteを割り当てます
var service = AccountSasService.parse("b"); // 使用したいStorageサービスの種類を指定します。今回はBlob(b)です
var resourceType = AccountSasResourceType.parse("oc"); // 操作を許可するリソースを選択します。Blobと、コンテナを操作したいのでObject(o≒blob)とContainer(c)を指定します
var expireTime = OffsetDateTime.now().plusSeconds(10 * 60); // 生成されるSASトークンの有効期限を指定します。OffsetDateTimeで指定しなければならないため、現在時刻+10分を割り当ててみます。
```

ここまで出来たら、blobClientのgenerateAccountSasを呼び出して、SASトークンを生成します。

```java
// 先ほどまで頑張って生成したAccountSas~などを引数に、AccountSasSignatureValuesを取得します
var sig = new AccountSasSignatureValues(expireTime, permission, service, resourceType);
// SASトークンが取得できます.
var sasToken = blobClient.generateAccountSas(sig);
```

これにより、取得されるトークンは以下のような形式になっております。

`?sv=2019-02-02&ss=b&srt=sco&sp=rwdlac&se=2020-03-21T13:30:24Z&st=2020-03-21T05:30:24Z&spr=https&sig=60eYuGBBTSRAbQ3%2BjNGcpdu63ohWKbPCpCkC%2FSrWuqw%3D`

このままでは接続文字列として使用できないので、以下のような形式につなげます。

`BlobEndpoint=${ストレージアカウント}.blob.core.windows.net/;TableEndpoint=${ストレージアカウント}.blob.core.windows.net/;SharedAccessSignature=${sasToken}`

これで、SASトークンを使用して期限などを設定した接続文字列として使用できます。

## ファイル操作を行う

SASトークン版の接続文字列を使用して、ストレージ操作を行ってみます！

クライアントの取得方法は同じですが、`BlobServiceClientBuilder#connectionString`メソッドに渡すパラメータをSASトークン版の接続文字列を使用します。

```java

var SAS_CONNECTION_STRING = "BlobEndpoint=storage-account.blob.core.windows.net/;TableEndpoint=storage-account.blob.core.windows.net/;SharedAccessSignature=?sv=2019-02-02&ss=b&srt=sco&sp=rwdlac&se=2020-03-21T13:30:24Z&st=2020-03-21T05:30:24Z&spr=https&sig=60eYuGBBTSRAbQ3%2BjNGcpdu63ohWKbPCpCkC%2FSrWuqw%3D";

var blobClient = new BlobServiceClientBuilder().connectionString(SAS_CONNECTION_STRING ).buildClient();
```

それでは、logコンテナに、ファイルをアップロードします。

```java

// logコンテナを取得します.
var logContainer = blobClient.getBlobContainerClient("log");

// logコンテナ内に、access.logというオブジェクトとしてアップロードします.
var accesslog = logContainer.getBlobClient("access.log");

// access.logに、「Hello, World!」と書き込みます。
try {
  var helloWorld = "Hello, World!".getBytes();
  accesslog.upload(new ByteArrayInputStream(helloWorld), helloWorld.length);
} catch (Exception e) {
  // なんかエラー処理とか
}
```

そのほかに、以下のようなメソッドが存在するようです。

* download

```java
accesslog.download(outputStream);
```

* delete

```java
accesslog.delete();
```

ひとまずファイル操作がある程度できるところまでまとめられました。

ちなみに、`getBlobClient`の際に、ファイル名を`/`区切りで指定した場合、仮想ディレクトリとして扱われます。

## 感想

JavaライブラリはV8版とV12版が存在しますが、Javaバージョンで違うのかな？と思っていましたが単純にライブラリのバージョン違いなだけのようです。
同じような処理をV8版を使用してやってみましたが、URISyntaxExceptionとかで死にました。
ライブラリを入れ替えたら楽勝でした。