雑記 |
Visual Basic 中学校 > 雑記 >
2021/1/24
この記事が対象とする製品・バージョン
Azure | ◎ | 対象です。 | |
AWS | × | 対象外です。 | |
GCP | × | 対象外です。 |
目次
文章内の [ ] は出典を示しています。出典は末尾に記載します。
この記事では、Azure ストレージのブロックBLOB をREST APIで読み書きする方法を説明します。
Azure ストレージは、Azure上にファイルなどを格納できるサービスです。
Azure ストレージにもいろいろな種類があるのですが、この記事で扱うのは最も一般的だと私が思うブロックBLOBです。Azureでストレージを扱う人のほとんどがこのブロックBLOBを中心に使用しているのではないかと思います。
Azure ストレージ | Azure BLOB | ブロックBLOB | なんでも保存できる。ドキュメント、動画、写真、テキスト、バイナリデータなど。 |
---|---|---|---|
ページBLOB | ランダムアクセスができる。 | ||
追加BLOB | 効率的に内容を追加できる。ログなど。 | ||
Azure ファイル | ファイル用。SMB。 | ||
Azure キュー | アプリケーション間のメッセージ用。 | ||
Azure テーブル | 構造化されたデータ用。NoSQL。 | ||
Azure ディスク | VMのドライブ(ボリューム) |
参考:[CORE]
ブロックBLOBの金額は使い方によって変わります。たとえば、1GBあたり月額 2.24円 などです[BLOBPRICE]。
Azure BLOBに保管されているドキュメントや動画や写真など各種データのことは「ファイル」とは呼ばず 「BLOB」(ブロブ)と呼びます。公式ドキュメントでは「ファイルを作成する」、「ファイルをダウンロードする」とは言わずに「BLOBを作成する」、「BLOBをダウンロードする」と表現されますので、言葉に慣れていない人はご注意ください。
BLOBを保管するサービスである Azure BLOB のことを 保管されているBLOB自体と区別する場合「BLOBサービス」と表現するようです。
ブロックBLOBを読み書きする手段はたくさん用意されています。この記事で扱うのは REST API です。
手段 | 特徴 | 適しているユーザー | |
---|---|---|---|
簡単 | Azure Storage Explorer [EXPLOR] | GUI。 | 一般寄りの利用者 |
↑ | Azureポータル | Webブラウザー。 | 管理者 |
AzCopy [AZCOPY] | コマンド | 運用担当者 | |
↓ | クライアントライブラリ [LIBS] | 各プログラミング言語で使用する専用のライブラリ | 開発者 |
面倒 | REST API [RESTTOC] | プログラミング言語で使用。汎用のWebAPI。 | 開発者 |
各プログラミング言語で使用するライブラリにはPowerShell用のものもあり、これを好む人もいるかもしれません。
私は技術上の興味をもって、この記事を書くにあたってはじめてAzureストレージに対してREST APIでアクセスしてみました。(だから、私はこの点では実績豊富な専門家というわけではありません。)
その感想としては、Azureストレージにアクセスする手段としてはREST APIは一番面倒で、できれば避けたいレベルです。単純にBLOBをダウンロード・アップロードするくらいであればそれほど面倒ではありませんが、それ以上の複雑な操作を行おうとするのであれば、選択すべきでないと感じました。
REST APIを選択する数少ない理由の1つは専用の環境が不要でプログラムに組み込める点です。上記の表でプログラムから呼び出しそうなものにはREST APIの他にAzCopyとクライアントライブラリがありますが、どちらも何らかのインストールや準備が必要です。REST APIはHTTP(S)通信でができる環境であればAzure用の特別な準備なしで使用できます。たとえば、Windows環境ではPowerShell、Linux環境でcurlやwgetなどの基本的な機能だけでも呼び出すことが可能です。
とはいえ、プログラムでクラウドを扱うのであればAWSであれ、GCPであれば、なにかしらプログラミング環境にインストールをするのが通常です。専用の環境が構築できないのは、何か特別な事情がある場合に限られるのではないかと思います。
念のためストレージアカウントとその中のBLOBの構造について少し説明しておくことにします。
Azureのサブスクリプションの中には複数のストレージアカウントを持つことができます。
それぞれのストレージアカウントの中に、「BLOB」サービスや「ファイル」サービスや「キュー」サービスなどがあります。
BLOBの中には、複数のコンテナーを作成できます。
ドキュメントや動画や写真、テキスト、バイナリーデーターなど保存したいものはコンテナーの中に保存します。
コンテナー中にはフォルダーのような階層構造は存在せず、すべてのBLOBがフラットに格納されます。
ただし、BLOBの名前には / が使用できるので、あたかもフォルダーが存在するかのような名前にすることができます。
たとえば、 「folder1/sample.txt」という名前の BLOBを作成できます。
Windowsであれば、 これに似た「folder1\sample.txt」という記述で、folder1フォルダー内にあるsample.txtという名前のファイルという意味になりますが、BLOBでは「folder1/sample.txt」という名前の1つのBLOBという扱いになります。
Web画面やツールでは、この点を利用してあたかもコンテナーの中にフォルダーが存在するかのような表示や操作を行えるものがありますが、実際には、BLOBの名前の一部をフィルタリングして表示していたりするだけです。
AzureストレージのREST APIの承認方法には次の4つの選択肢があります[AUTH]。
・ストレージアカウント アクセスキーを使って承認する
・Shared Access Signature (SAS)を使って承認する
・Azure Active Directory を使って承認する
・承認しない。(誰でもアクセスできる状態にする)
この記事では、ストレージアカウント アクセスキー(以下、アクセスキー)を使って承認する方法を採用します。
アクセスキーを使用する場合、アクセスキーが漏洩しないように注意してください。クライアント側で動作するアプリケーションやブラウザー上で動作するスクリプトでアクセスキーを扱うことは漏洩につながります。基本的にはアクセスキーをユーザー自身に入力させるユーティリティー的なソフトウェアか、サーバー側で動作するアプリケーションとして使用することになると思います。
なお、万一アクセスキーが漏洩した場合、アクセスキーを更新することでそれまでのアクセスキーを無効して新しいアクセスキーを発行することができます。
アクセスキーは、ストレージアカウントのストレージアカウントキーはAzureポータルのストレージ画面でアクセスキーの設定から取得できます。
アクセスキー自体は、下記のような文字列です。
VGhpcyBpcyBhbiBBenVyZSBzdG9yYWdlIGFjY2VzcyBrZXkgc2FtcGxlLg==
アクセスキーがあると第三者からストレージを読み書きされてしまうので、このアクセスキーは公開しないように気をつけて管理してください。画面にある、更新アイコンをクリックすると、これまでのアクセスキーを無効にして新しいアクセスキーを取得することもできます。
具体的なプログラムを紹介する前に、このアクセスキーがどのように使われるか説明しておきます。アクセスキーそのものをリクエストに含めるわけではありません。
この説明はなれない人には少し難しいのですが、とりあえず動くサンプルもすぐ後で紹介しますのでご心配なく。
REST APIでリクエストを行うHTTPには承認以外では下記のような情報が含まれることになります。これ以外にも含まれますがアクセスキーとは関係ないので省きます。
AzureストレージのREST APIでは、この中から特定の情報を結合してHMAC-SHA256で暗号化したものを承認情報として使用します。この暗号化するときのシークレットキーとなるのがストレージアカウントのアクセスキーです。
このように生成された承認情報をHTTPヘッダーのAuthorizationにセットして送信します。受信側では、この情報を復号し、リクエスト内容と一致しているか検証して正規のリクエストかどうか判断するわけです。
以上で説明したようにアクセスキーを基に承認情報を生成するプログラムは、暗号化する元となる情報を収集して決まった順序・形式で結合する必要があるので面倒ですが、一定のルールなので一度プログラムしてしまえば汎用で使用できそうです。
このルールを実装したサンプルコードがこちらです。このサンプルコードはAuthorizationヘッダーを生成します。後で紹介するBLOBにアクセスするサンプルではここ紹介するGenerateAuthorizationHeaderメソッドでAuthorizationヘッダーを生成します。
このコードはこの記事で紹介するサンプルプログラムを動作させるために作成しただけで、十分にテストされたものでもなければ実績のあるものでもありません。特にHTTPヘッダーを文字列かするルールに一部不明瞭なところがあるので、コメントにしてあるヘッダーを使用するときは何かしなければいけないと思います。しかし、サンプルプログラムではちゃんと役目を果たしているので参考になるレベルだとも思います。
この実装に問題がある場合は、下記サイトを参考にしてみてください。
共有キーでの承認 (REST API) - Azure ストレージ | Microsoft Docs
下記の記事では、承認情報の作成方法を説明しており、サンプルのダウンロードもできます。
VB
''' <summary> ''' Azure Storage REST APIのリクエストで使用するAuthorizationヘッダーを生成します。 ''' 仕様で定められているヘッダー等の値を収集して、最後にハッシュを計算して、Authorizationヘッダーを生成します。 ''' https://docs.microsoft.com/ja-jp/azure/storage/common/storage-rest-api-auth#creating-the-authorization-header ''' </summary> ''' <param name="request">REST APIのリクエスト。Authorizationヘッダー以外の設定がすべて完了した状態のものを渡してください。</param> ''' <param name="storageAccessKey">対象のストレージアカウントのアクセスキー</param> Private Function GenerateAuthorizationHeader(request As System.Net.Http.HttpRequestMessage, storageAccessKey As String) As Net.Http.Headers.AuthenticationHeaderValue '●1.情報収集 Dim fields As New List(Of String) '▼1-1.一般のヘッダーの値を収集します。 fields.Add(request.Method.ToString) fields.Add("") 'request.Content.Headers.ContentEncoding fields.Add("") 'request.Content.Headers.ContentLanguage fields.Add(request.Content?.Headers.ContentLength.ToString) fields.Add("") 'request.Content.Headers.ContentMD5 fields.Add("") 'request.Content.Headers.ContentType fields.Add("") 'request.Headers.Date fields.Add("") 'request.Headers.IfModifiedSince fields.Add("") 'request.Headers.IfMatch fields.Add("") 'request.Headers.IfNoneMatch fields.Add("") 'request.Headers.IfUnmodifiedSince fields.Add("") 'request.Headers.Range '▼1-2.x-ms- ヘッダーの値を収集します。 Dim xmsHeaders = From header In request.Headers Where header.Key.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase) Order By header.Key For Each xmsHeader In xmsHeaders fields.Add(xmsHeader.Key.ToLower & ":" & String.Join(",", xmsHeader.Value)) Next '▼1-3.正規化されたリソースとクエリパラメーターを収集します。 'https://accoutName.blob.core.windows.net/xxxxx のようなURLから accountName を取得します。 Dim storageAccountName As String = request.RequestUri.Host.Split(".")(0) fields.Add($"/{storageAccountName}{request.RequestUri.AbsolutePath}".ToLower) Dim queries As Specialized.NameValueCollection = Web.HttpUtility.ParseQueryString(request.RequestUri.Query) For Each key In queries.AllKeys.OrderBy(Function(k) k) fields.Add((key.ToLower & ":" & queries(key))) Next '▼1-4.収集した情報を結合します。 Dim signature As String = String.Join(vbLf, fields) '●2.ハッシュを計算 Dim bytes As Byte() = System.Text.Encoding.UTF8.GetBytes(signature) Dim SHA256 As New Security.Cryptography.HMACSHA256(Convert.FromBase64String(storageAccessKey)) Dim rawSignature As String = Convert.ToBase64String(SHA256.ComputeHash(bytes)) '●3.Authorizationヘッダー生成 Dim authorizationHeader As New Net.Http.Headers.AuthenticationHeaderValue("SharedKey", storageAccountName & ":" & rawSignature) Return authorizationHeader End Function |
C#
/// <summary> /// Azure Storage REST APIのリクエストで使用するAuthorizationヘッダーを生成します。 /// 仕様で定められているヘッダー等の値を収集して、最後にハッシュを計算して、Authorizationヘッダーを生成します。 /// https://docs.microsoft.com/ja-jp/azure/storage/common/storage-rest-api-auth#creating-the-authorization-header /// </summary> /// <param name="request">REST APIのリクエスト。Authorizationヘッダー以外の設定がすべて完了した状態のものを渡してください。</param> /// <param name="storageAccessKey">対象のストレージアカウントのアクセスキー</param> private System.Net.Http.Headers.AuthenticationHeaderValue GenerateAuthorizationHeader(System.Net.Http.HttpRequestMessage request, string storageAccessKey) { //●1.情報収集 var fields = new List<string>(); //▼1-1.一般のヘッダーの値を収集します。 fields.Add(request.Method.ToString()); fields.Add(""); //request.Content.Headers.ContentEncoding fields.Add(""); //request.Content.Headers.ContentLanguage fields.Add(request.Content?.Headers.ContentLength.ToString()); fields.Add(""); //request.Content.Headers.ContentMD5 fields.Add(""); //request.Content.Headers.ContentType fields.Add(""); //request.Headers.Date fields.Add(""); //request.Headers.IfModifiedSince fields.Add(""); //request.Headers.IfMatch fields.Add(""); //request.Headers.IfNoneMatch fields.Add(""); //request.Headers.IfUnmodifiedSince fields.Add(""); //request.Headers.Range //▼1-2.x-ms- ヘッダーの値を収集します。 var xmsHeaders = from header in request.Headers where header.Key.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase) orderby header.Key select header; foreach (var xmsHeader in xmsHeaders) { fields.Add(xmsHeader.Key.ToLower() + ":" + string.Join(",", xmsHeader.Value)); } //▼1-3.正規化されたリソースとクエリパラメーターを収集します。 //https://accoutName.blob.core.windows.net/xxxxx のようなURLから accountName を取得します。 string storageAccountName = request.RequestUri.Host.Split(".")[0]; fields.Add($"/{storageAccountName}{request.RequestUri.AbsolutePath}".ToLower()); System.Collections.Specialized.NameValueCollection queries = System.Web.HttpUtility.ParseQueryString(request.RequestUri.Query); foreach (string key in queries.AllKeys.OrderBy(k => k)) { fields.Add(key.ToLower() + ":" + queries[key]); } //▼1-4.収集した情報を結合します。 string signature = string.Join('\n', fields); //●2.ハッシュを計算 byte[] bytes = System.Text.Encoding.UTF8.GetBytes(signature); var SHA256 = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(storageAccessKey)); string rawSignature = Convert.ToBase64String(SHA256.ComputeHash(bytes)); //●3.Authorizationヘッダー生成 var authorizationHeader = new System.Net.Http.Headers.AuthenticationHeaderValue("SharedKey", storageAccountName + ":" + rawSignature); return authorizationHeader; } // GenerateAuthorizationHeader |
アクセスキーを使う場合に必ず指定すべきHTTPヘッダーが2つあります。1つ目が Date または x-ms-date です[SKEY]。
Dateヘッダーは一部のツールが自動的に使用する場合があるので、x-ms-dateヘッダーを使用するのが良いようです[SKEY]。
このヘッダーには、リクエスト時の日時をUTC(世界協定時)で、RFC1123で規定する形式で指定する必要があります([PG]のサンプルコードからの推測)。
VB/C#では、次のようなプログラムで簡単にこの値を生成できます。
VB
Dim utcNow As String = Date.UtcNow.ToString("R", Globalization.CultureInfo.InvariantCulture) |
C#
string utcNow = DateTime.UtcNow.ToString("R", System.Globalization.CultureInfo.InvariantCulture); |
このヘッダーで指定する日時から15以上経っている場合、リクエストは失敗します。これはリクエストを保存して後で攻撃に利用されないようにするためです[SKEY]。このヘッダーの値はAuthorizationヘッダーのハッシュの計算元の1つなので、後で日時だけ変更しても、Authorizationヘッダーを再設定できない限りリクエストが再利用できないという仕組みです。
もう1つの必須ヘッダーは x-ms-version です。このヘッダーには使用するAzure ストレージのバージョンを指定します[VER]。バージョンは「2020-04-08」のような形式の文字列です。
特に理由がなければ最新バージョンを指定するのが良く、最新バージョンはこのドキュメントに記載されています。
Azure Storage services のバージョン管理 | Microsoft Docs
特定の機能や操作を使用する場合は、過去のバージョンを指定する必要があるなどの制約があるようです。
それでは、まず、BLOBの中にあるものをREST APIを使ってダウンロードしてみましょう。
Get BLOBというAPIが使用できます[GETBLOB]。
リファレンスはこちらです。私は2020年12月20日にこのリファレンスを見てこの文章を書いています。
https://docs.microsoft.com/ja-jp/rest/api/storageservices/get-blob
リファレンスを見ると送信すべきリクエストについて次のことがわかります。
以上により、Authorizationヘッダーさえ準備できれば呼び出し自体は特に難しいことはなさそうです。
レスポンスの方も素直に本文に要求したBLOBの内容が書き込まれていますので、それを読み取ってローカルファイルとして保存すればダウンロード完了となります。
その一連の処理を実装すると次のようになります。
このプログラムでは folder1/sample.txt という名前のBLOBを取得してローカルのC:\temp\folder1\sample.txtという名前で保存します。
BLOBの名前など冒頭で定義している4つの変数の値はみなさんの環境にあうように値を設定してください。
VB
Dim storageAccountName
As String =
"storageAccountName"
'ストレージアカウントの名前 Dim storageAccessKey As String = "VGhpcyBpcyBhbiBBenVyZSBzdG9yYWdlIGFjY2VzcyBrZXkgc2FtcGxlLg==" 'ストレージアカウントのアクセスキー Dim containerName As String = "containerName" 'コンテナーの名前 Dim blobName As String = "folder1/sample.txt" '取得するBLOBの名前 'Get BLOB API のURL 'https://myaccount.blob.core.windows.net/mycontainer/myblob Dim url As String = $"https://{storageAccountName}.blob.core.windows.net/{containerName}/{blobName}" Using request As New Net.Http.HttpRequestMessage(Net.Http.HttpMethod.Get, url) '必須ヘッダー Dim utcNow As String = Date.UtcNow.ToString("R", Globalization.CultureInfo.InvariantCulture) request.Headers.Add("x-ms-date", utcNow) request.Headers.Add("x-ms-version", "2020-04-08") 'すべての準備が整ったらハッシュを計算して、Authorizationヘッダーを生成します。 Dim authorizationHeader = GenerateAuthorizationHeader(request, storageAccessKey) request.Headers.Authorization = authorizationHeader '▼WebAPIを呼び出します。 Using client As New System.Net.Http.HttpClient() Using response As System.Net.Http.HttpResponseMessage = client.SendAsync(request).GetAwaiter.GetResult '成功しなかった場合例外を発生させます。 response.EnsureSuccessStatusCode() 'レスポンス本文を読み取ります。 Dim responseBody As Byte() = response.Content.ReadAsByteArrayAsync().GetAwaiter.GetResult 'レスポンスをローカルのファイルとして保存します。 Dim localFilePath As String = "C:\temp\" & blobName If IO.Directory.Exists(IO.Path.GetDirectoryName(localFilePath)) = False Then IO.Directory.CreateDirectory(IO.Path.GetDirectoryName(localFilePath)) End If IO.File.WriteAllBytes(localFilePath, responseBody) Debug.WriteLine($"{localFilePath} に保存しました。") End Using End Using End Using |
C#
string storageAccountName =
"storageAccountName";
//ストレージアカウントの名前 string storageAccessKey = "VGhpcyBpcyBhbiBBenVyZSBzdG9yYWdlIGFjY2VzcyBrZXkgc2FtcGxlLg=="; //ストレージアカウントのアクセスキー string containerName = "containerName"; //コンテナーの名前 string blobName = "folder1/sample.txt"; //取得するBLOBの名前 //Get BLOB API のURL //https://myaccount.blob.core.windows.net/mycontainer/myblob string url = $"https://{storageAccountName}.blob.core.windows.net/{containerName}/{blobName}"; using (var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url)) { //必須ヘッダー string utcNow = DateTime.UtcNow.ToString("R", System.Globalization.CultureInfo.InvariantCulture); request.Headers.Add("x-ms-date", utcNow); request.Headers.Add("x-ms-version", "2020-04-08"); //すべての準備が整ったらハッシュを計算して、Authorizationヘッダーを生成します。 var authorizationHeader = GenerateAuthorizationHeader(request, storageAccessKey); request.Headers.Authorization = authorizationHeader; //▼WebAPIを呼び出します。 using (var client = new System.Net.Http.HttpClient()) { using (System.Net.Http.HttpResponseMessage response = client.SendAsync(request).GetAwaiter().GetResult()) { //成功しなかった場合例外を発生させます。 response.EnsureSuccessStatusCode(); //レスポンス本文を読み取ります。 byte[] responseBody = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult(); //レスポンスをローカルのファイルとして保存します。 string localFilePath = @"C:\temp\" + blobName; if (System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(localFilePath)) == false) { System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(localFilePath)); } System.IO.File.WriteAllBytes(localFilePath, responseBody); System.Diagnostics.Debug.WriteLine($"{localFilePath} に保存しました。"); } //using response } //using client } //using request |
このプログラムでは、まず必須のHTTPヘッダーである x-ms-date と x-ms-version を設定しています。この2つのヘッダーについては既に説明したとおりです。
他に設定すべきヘッダーがないので、その後GerenrateAuthorizationHeaderメソッドを使用してAuthorizationヘッダーを生成しています。GenerateAuthorizationHeaderのソースコードは前述しています。
これでリクエストの準備は整うので、HttpClientクラスのSendAsyncメソッドを使ってリクエストを送信します。このメソッドは非同期で実行されるため、GetAwaiter.GetResult を使って同期実行させています。ここは Await を使って次のように書くほうが優れています。
Using response As System.Net.Http.HttpResponseMessage = Await client.SendAsync(request) |
このようにしなかった理由は、サンプルの掲載のしやすさです。Await を使う場合、このコードを含むプロシージャを Async で宣言する必要があります。つまり、ここに掲載しているサンプルをコピー&貼り付けすればすぐに動作するわけではなくなるということです。Asyncを付けるのは難しいことではないかもしれませんが単純に付ければいいものでもなく、この知識がなくてかなり困る人もいることでしょう。
これだけの理由なのでAsyncを付けられる知識のある人は Await の方に書き換えて使ってください。
後の流れはコメントを読めばわかると思います。
ローカルのファイルをBLOBにアップロードするにはPut BLOBというAPIを使用します[PUTBLOB]。
リファレンスはこちらです。私は2020年12月24日にこのリファレンスを見てこの文章を書いています。
https://docs.microsoft.com/ja-jp/rest/api/storageservices/put-blob
リファレンスを見ると送信すべきリクエストについて次のことがわかります。
以上により、Authorizationヘッダーさえ準備できれば呼び出し自体は特に難しいことはなさそうです。
その一連の処理を実装すると次のようになります。
冒頭で定義している5つの変数の値はみなさんの環境にあうように値を設定してください。
VB
Dim storageAccountName
As String =
"storageAccountName"
'ストレージアカウントの名前 Dim storageAccessKey As String = "VGhpcyBpcyBhbiBBenVyZSBzdG9yYWdlIGFjY2VzcyBrZXkgc2FtcGxlLg==" 'ストレージアカウントのアクセスキー Dim containerName As String = "containerName" 'コンテナーの名前 Dim blobName As String = "folder1/sample.txt" '対象のBLOBの名前 Dim localFileName As String = "C:\temp\test.txt" 'アップロードするファイル名 'Put BLOB API のURL 'https://myaccount.blob.core.windows.net/mycontainer/myblob Dim url As String = $"https://{storageAccountName}.blob.core.windows.net/{containerName}/{blobName}" Using request As New Net.Http.HttpRequestMessage(Net.Http.HttpMethod.Put, url) 'リクエスト本文にファイルの内容を書き込む request.Content = New System.Net.Http.ByteArrayContent(IO.File.ReadAllBytes(localFileName)) '必須ヘッダー Dim utcNow As String = Date.UtcNow.ToString("R", Globalization.CultureInfo.InvariantCulture) request.Headers.Add("x-ms-date", utcNow) request.Headers.Add("x-ms-version", "2020-04-08") request.Headers.Add("x-ms-blob-type", "BlockBlob") 'すべての準備が整ったらハッシュを計算して、Authorizationヘッダーを生成します。 Dim authorizationHeader = GenerateAuthorizationHeader(request, storageAccessKey) request.Headers.Authorization = authorizationHeader '▼WebAPIを呼び出します。 Using client As New System.Net.Http.HttpClient() Using response As System.Net.Http.HttpResponseMessage = client.SendAsync(request).GetAwaiter.GetResult '成功しなかった場合例外を発生させます。 response.EnsureSuccessStatusCode() Debug.WriteLine($"正常に終了") End Using End Using End Using |
C#
string storageAccountName =
"storageAccountName";
//ストレージアカウントの名前 string storageAccessKey = "VGhpcyBpcyBhbiBBenVyZSBzdG9yYWdlIGFjY2VzcyBrZXkgc2FtcGxlLg=="; //ストレージアカウントのアクセスキー string containerName = "containerName"; //コンテナーの名前 string blobName = "folder1/sample.txt"; ///対象のBLOBの名前 string localFileName = @"C:\temp\test.txt"; //アップロードするファイル名 //Put BLOB API のURL //https://myaccount.blob.core.windows.net/mycontainer/myblob string url = $"https://{storageAccountName}.blob.core.windows.net/{containerName}/{blobName}"; using (var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Put, url)) { //リクエスト本文にファイルの内容を書き込む request.Content = new System.Net.Http.ByteArrayContent(System.IO.File.ReadAllBytes(localFileName)); //必須ヘッダー string utcNow = DateTime.UtcNow.ToString("R", System.Globalization.CultureInfo.InvariantCulture); request.Headers.Add("x-ms-date", utcNow); request.Headers.Add("x-ms-version", "2020-04-08"); request.Headers.Add("x-ms-blob-type", "BlockBlob"); //すべての準備が整ったらハッシュを計算して、Authorizationヘッダーを生成します。 var authorizationHeader = GenerateAuthorizationHeader(request, storageAccessKey); request.Headers.Authorization = authorizationHeader; //▼WebAPIを呼び出します。 using (var client = new System.Net.Http.HttpClient()) { using (System.Net.Http.HttpResponseMessage response = client.SendAsync(request).GetAwaiter().GetResult()) { //成功しなかった場合例外を発生させます。 response.EnsureSuccessStatusCode(); System.Diagnostics.Debug.WriteLine($"正常に終了"); } //using response } //using client } //using request |
この例をみるとわかるように、HttpClientを使ったHTTPリクエストの送信では、リクエストの本文は HttpRequestMessageのContentプロパティに設定します。リクエスト本文の内容はいろいろありえますが、バイナリーデーターであれば何でも対応できるので今回はByteArrayContentを設定しています。プログラムの都合によっては、StringContentなどを使用することもできます。
x-ms-blob-typeヘッダーに設定すべき値はリファレンス([PUTBLOB])に BlockBlob または PageBlob または AppendBlob であることが記載されています。親切に説明はされていませんが、英語の意味からそれぞれBlockBlob(ブロックBLOB)、PageBlob(ページBLOB)、AppendBlob(追加BLOB)であることがわかります。
このAPIはレスポンスのHTTPステータスコードが 201 (Created) であれば、成功であるということなので、EnsureSuccessStatusCodeで例外が発生しない場合は無条件で成功としました。
最後にストレージアカウント内にあるBLOBのコンテナー一覧を取得する例を紹介します。
このために List BLOBs というAPIを使用します[LISTBLOBS]。
リファレンスはこちらです。私は2020年12月26日にこのリファレンスを見てこの文章を書いています。
https://docs.microsoft.com/ja-jp/rest/api/storageservices/list-blobs
List BLOBsはレスポンスとしてXMLを受け取ります。このXMLには、BLOBの名前やサイズ、更新日などの情報が入っているので、プログラムでXMLを読み取って必要な情報を取得する必要があります。
この点がGET BLOBとの主な違いです。他の点ではList BLOBsの呼び出しは Get BLOBとほぼ同じで、HTTPメソッドも GET です。
リファレンスを見ると送信すべきリクエストについて次のことがわかります。
さらに、(Get BLOBやPut BLOBのときと同様で)リファレンスには明示されていませんが、次のことが推測できます。
以上により、他のAPIと同様で、Authorizationヘッダーさえ準備できれば呼び出し自体は特に難しいことはなさそうです。
prefixというURIパラメーターを使用すると、指定した名前からはじまるBLOBだけを取得することもできます。
このようにいくつかの使い方はありますが、単純に処理を実装すると次のようになります。後で説明しますがこの例では最大で5000件のBLOBを取得します。
冒頭で定義している3つの変数の値はみなさんの環境にあうように値を設定してください。
VB
Dim storageAccountName
As String =
"storageAccountName"
'ストレージアカウントの名前 Dim storageAccessKey As String = "VGhpcyBpcyBhbiBBenVyZSBzdG9yYWdlIGFjY2VzcyBrZXkgc2FtcGxlLg==" 'ストレージアカウントのアクセスキー Dim containerName As String = "containerName" 'コンテナーの名前 'List BLOB API のURL 'https://myaccount.blob.core.windows.net/mycontainer?restype=container&comp=list Dim url As String = $"https://{storageAccountName}.blob.core.windows.net/{containerName}?restype=container&comp=list" Using request As New Net.Http.HttpRequestMessage(Net.Http.HttpMethod.Get, url) '必須ヘッダー Dim utcNow As String = Date.UtcNow.ToString("R", Globalization.CultureInfo.InvariantCulture) request.Headers.Add("x-ms-date", utcNow) request.Headers.Add("x-ms-version", "2020-04-08") 'すべての準備が整ったらハッシュを計算して、Authorizationヘッダーを生成します。 Dim authorizationHeader = GenerateAuthorizationHeader(request, storageAccessKey) request.Headers.Authorization = authorizationHeader '▼WebAPIを呼び出します。 Using client As New System.Net.Http.HttpClient() Using response As System.Net.Http.HttpResponseMessage = client.SendAsync(request).GetAwaiter.GetResult '成功しなかった場合例外を発生させます。 response.EnsureSuccessStatusCode() 'レスポンス本文を読み取ります。 Dim responseBody As String = response.Content.ReadAsStringAsync().GetAwaiter.GetResult Debug.WriteLine("生のレスポンス全体") Debug.WriteLine(responseBody) 'レスポンスのXMLを解析してコンテナー名を抜き出します。 Debug.WriteLine("BLOB一覧") Debug.WriteLine($"{"名前",-18}{"サイズ(バイト)",14}{"作成日",17}{"最終更新日",15}") '各数値は出力する幅です。 Dim document As New Xml.XmlDocument document.LoadXml(responseBody) Dim blobInfos As Xml.XmlNodeList = document.SelectNodes("//EnumerationResults/Blobs/Blob") For Each blobInfo As Xml.XmlElement In blobInfos 'BLOBの名前 Dim name As String = blobInfo.SelectSingleNode("Name").InnerText '詳細情報 Dim details As Xml.XmlElement = CType(blobInfo.SelectSingleNode("Properties"), Xml.XmlElement) Dim contentLength As Long = Long.Parse(details("Content-Length").InnerText) 'サイズ(バイト) Dim createionTime As Date = Date.Parse(details("Creation-Time").InnerText) '作成日 Dim lastModified As Date = Date.Parse(details("Last-Modified").InnerText) '最終更新日 '各項目幅20でそろえて出力 '各項目は20文字を超えると出力位置がずれます。 'またこの書式は「いわゆる全角文字」を考慮しないため、「いわゆる全角文字」を含む場合位置がずれます。 'あくまで簡易的なものと考えてください。 Debug.WriteLine($"{name,-20}{contentLength,20}{createionTime,20}{lastModified,20}") Next End Using End Using End Using |
C#
string storageAccountName =
"storageAccountName";
//ストレージアカウントの名前 string storageAccessKey = "VGhpcyBpcyBhbiBBenVyZSBzdG9yYWdlIGFjY2VzcyBrZXkgc2FtcGxlLg=="; //ストレージアカウントのアクセスキー string containerName = "containerName"; //コンテナーの名前 //List BLOB API のURL //https://myaccount.blob.core.windows.net/mycontainer?restype=container&comp=list string url = $"https://{storageAccountName}.blob.core.windows.net/{containerName}?restype=container&comp=list"; using (var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url)) { //必須ヘッダー string utcNow = DateTime.UtcNow.ToString("R", System.Globalization.CultureInfo.InvariantCulture); request.Headers.Add("x-ms-date", utcNow); request.Headers.Add("x-ms-version", "2020-04-08"); //すべての準備が整ったらハッシュを計算して、Authorizationヘッダーを生成します。 var authorizationHeader = GenerateAuthorizationHeader(request, storageAccessKey); request.Headers.Authorization = authorizationHeader; //▼WebAPIを呼び出します。 using (var client = new System.Net.Http.HttpClient()) { using (System.Net.Http.HttpResponseMessage response = client.SendAsync(request).GetAwaiter().GetResult()) { //成功しなかった場合例外を発生させます。 response.EnsureSuccessStatusCode(); //レスポンス本文を読み取ります。 string responseBody = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); System.Diagnostics.Debug.WriteLine("生のレスポンス全体"); System.Diagnostics.Debug.WriteLine(responseBody); //レスポンスのXMLを解析してコンテナー名を抜き出します。 System.Diagnostics.Debug.WriteLine("BLOB一覧"); System.Diagnostics.Debug.WriteLine($"{"名前",-18}{"サイズ(バイト)",14}{"作成日",17}{"最終更新日",15}"); //各数値は出力する幅です。 var document = new System.Xml.XmlDocument(); document.LoadXml(responseBody); System.Xml.XmlNodeList blobInfos = document.SelectNodes("//EnumerationResults/Blobs/Blob"); foreach (System.Xml.XmlElement blobInfo in blobInfos) { //BLOBの名前 string name = blobInfo.SelectSingleNode("Name").InnerText; //詳細情報 System.Xml.XmlElement details = (System.Xml.XmlElement)blobInfo.SelectSingleNode("Properties"); long contentLength = long.Parse(details["Content-Length"].InnerText); //サイズ(バイト) DateTime createionTime = DateTime.Parse(details["Creation-Time"].InnerText); //作成日 DateTime lastModified = DateTime.Parse(details["Last-Modified"].InnerText); //最終更新日 //各項目幅20でそろえて出力 //各項目は20文字を超えると出力位置がずれます。 //またこの書式は「いわゆる全角文字」を考慮しないため、「いわゆる全角文字」を含む場合位置がずれます。 //あくまで簡易的なものと考えてください。 System.Diagnostics.Debug.WriteLine($"{name,-20}{contentLength,20}{createionTime,20}{lastModified,20}"); } //foreach blobInfo } //using response } //using client } //using request |
リクエスト部分はURLと必須ヘッダーが多少違うだけでGet BLOBと同じなので説明は割愛します。
レスポンスはXML形式で次の内容を受け取ります[LISTBLOBS]。状況によってはもっと別の情報が追加されている場合もあります。また、この内容はクラウド側のバージョンアップにより変更される可能性があるので最新情報は公式ドキュメントをご確認ください。
<?xml version="1.0" encoding="utf-8"?> <EnumerationResults ServiceEndpoint="https://storageAccountName.blob.core.windows.net/" ContainerName="containerName"> <Blobs> <Blob> <Name>folder1/sample.txt</Name> <Properties> <Creation-Time>Thu, 24 Dec 2020 11:46:23 GMT</Creation-Time> <Last-Modified>Thu, 24 Dec 2020 11:46:23 GMT</Last-Modified> <Etag>0x8D8A8018A4F2194</Etag> <Content-Length>70</Content-Length> <Content-Type>application/octet-stream</Content-Type> <Content-Encoding /> <Content-Language /> <Content-CRC64 /> <Content-MD5>LV+m88QL49mnVpy+260Ejg==</Content-MD5> <Cache-Control /> <Content-Disposition /> <BlobType>BlockBlob</BlobType> <AccessTier>Hot</AccessTier> <AccessTierInferred>true</AccessTierInferred> <LeaseStatus>unlocked</LeaseStatus> <LeaseState>available</LeaseState> <ServerEncrypted>true</ServerEncrypted> </Properties> <OrMetadata /> </Blob> <Blob> ・・・2つ目ののBLOB情報・・・ </Blob> <Blob> ・・・3つ目ののBLOB情報・・・以下同様に続きます。 </Blob> </Blobs> <NextMarker /> </EnumerationResults> |
この構造をプログラムと照らし合わせると、どのようにXMLを読み込んでいるかわかると思います。
VB/C#でXMLを読み取る方法は下記の記事で説明していますので、よろしければ参考にしてください。
単純に一覧を取得するとBLOBが大量にあるコンテナーを指定するとレスポンスも大量になります。既定では最大5000のBLOB情報を返します。最大値は maxresults というURIパラメーターを使って変更できますが 5000以上にすることはできません。大量のBLOBの一覧を取得したい場合は、List BLOBs APIを何度も呼び出す必要があります。2回目の呼び出しが1回目の呼び出しの続きであることを示すには2回目の呼び出しで marker というURIパラメーターを指定します。markerパラメーターに指定すべき値は1回目の呼び出しのレスポンスのNextMarkerという項目に含まれます。つまり、レスポンスに含まれるNextMarkerを次のList BLOBsの呼び出しで指定することで続きが取得できるというわけです。このため、maxResultsはある程度小さい数字に絞って、必要になる都度一覧を要求するほうがスマートです。
継ぎの例ではListBlobsWithPagingメソッドを呼び出すと10件ずつ3回にわけてBLOBを取得します。つまり合計で30件のBLOBの名前が出力されます。
VB
Private Sub ListBlobsWithPaging() Dim storageAccountName As String = "storageAccountName" 'ストレージアカウントの名前 Dim storageAccessKey As String = "VGhpcyBpcyBhbiBBenVyZSBzdG9yYWdlIGFjY2VzcyBrZXkgc2FtcGxlLg==" 'ストレージアカウントのアクセスキー Dim containerName As String = "containerName" 'コンテナーの名前 Dim nextMarker As String '1回目の呼び出し。とりあえず10件取得する。 nextMarker = ListBlobs(storageAccountName, storageAccessKey, containerName, 10, Nothing) '続きがあれば2回目の呼び出し。次の10件を取得する。 If Len(nextMarker) > 0 Then nextMarker = ListBlobs(storageAccountName, storageAccessKey, containerName, 10, nextMarker) End If '続きがあれば3回目の呼び出し。次の10件を取得する。 If Len(nextMarker) > 0 Then nextMarker = ListBlobs(storageAccountName, storageAccessKey, containerName, 10, nextMarker) End If End Sub ''' <summary> ''' コンテナーからBLOBの一覧を取得して名前をDebug.WriteLineで出力します。 ''' </summary> ''' <param name="storageAccountName">対象のストレージアカウントの名前</param> ''' <param name="storageAccessKey">対象のストレージアカウントのアクセスキー</param> ''' <param name="containerName">対象のコンテナーの名前</param> ''' <param name="maxResults">取得する最大数。最大値は5000</param> ''' <param name="marker">前回の続きを取得する前回の戻り値を指定します。そうでない場合Nothing</param> ''' <returns>次回続きを取得するのに使用するmarkerの値。</returns> Private Function ListBlobs(storageAccountName As String, storageAccessKey As String, containerName As String, maxResults As Integer, marker As String) As String 'List BLOB API のURL Dim url As String = $"https://{storageAccountName}.blob.core.windows.net/{containerName}?restype=container&comp=list&maxresults={maxResults}" If Not String.IsNullOrEmpty(marker) Then url &= "&marker=" & marker End If Using request As New Net.Http.HttpRequestMessage(Net.Http.HttpMethod.Get, url) '必須ヘッダー Dim utcNow As String = Date.UtcNow.ToString("R", Globalization.CultureInfo.InvariantCulture) request.Headers.Add("x-ms-date", utcNow) request.Headers.Add("x-ms-version", "2020-04-08") 'すべての準備が整ったらハッシュを計算して、Authorizationヘッダーを生成します。 Dim authorizationHeader = GenerateAuthorizationHeader(request, storageAccessKey) request.Headers.Authorization = authorizationHeader '▼WebAPIを呼び出します。 Using client As New System.Net.Http.HttpClient() Using response As System.Net.Http.HttpResponseMessage = client.SendAsync(request).GetAwaiter.GetResult '成功しなかった場合例外を発生させます。 response.EnsureSuccessStatusCode() 'レスポンス本文を読み取ります。 Dim responseBody As String = response.Content.ReadAsStringAsync().GetAwaiter.GetResult Debug.WriteLine("生のレスポンス全体") Debug.WriteLine(responseBody) 'レスポンスのXMLを解析してコンテナー名を抜き出します。 Debug.WriteLine("BLOB一覧") Debug.WriteLine($"{"名前",-18}{"サイズ(バイト)",14}{"作成日",17}{"最終更新日",15}") '各数値は出力する幅です。 Dim document As New Xml.XmlDocument document.LoadXml(responseBody) Dim blobInfos As Xml.XmlNodeList = document.SelectNodes("//EnumerationResults/Blobs/Blob") For Each blobInfo As Xml.XmlElement In blobInfos 'BLOBの名前 Dim name As String = blobInfo.SelectSingleNode("Name").InnerText '詳細情報 Dim details As Xml.XmlElement = CType(blobInfo.SelectSingleNode("Properties"), Xml.XmlElement) Dim contentLength As Long = Long.Parse(details("Content-Length").InnerText) 'サイズ(バイト) Dim createionTime As Date = Date.Parse(details("Creation-Time").InnerText) '作成日 Dim lastModified As Date = Date.Parse(details("Last-Modified").InnerText) '最終更新日 '各項目幅20でそろえて出力 '各項目は20文字を超えると出力位置がずれます。 'またこの書式は「いわゆる全角文字」を考慮しないため、「いわゆる全角文字」を含む場合位置がずれます。 'あくまで簡易的なものと考えてください。 Debug.WriteLine($"{name,-20}{contentLength,20}{createionTime,20}{lastModified,20}") Next 'NextMarkerの取得 Dim nextMarkerNode As Xml.XmlNode = document.SelectSingleNode("//EnumerationResults/NextMarker") Dim nextMarker As String = nextMarkerNode.InnerText Return nextMarker End Using End Using End Using End Function |
C#
private void ListBlobsWithPaging() { string storageAccountName = "storageAccountName"; //ストレージアカウントの名前 string storageAccessKey = "VGhpcyBpcyBhbiBBenVyZSBzdG9yYWdlIGFjY2VzcyBrZXkgc2FtcGxlLg=="; //ストレージアカウントのアクセスキー string containerName = "containerName"; //コンテナーの名前 string nextMarker; //1回目の呼び出し。とりあえず10件取得する。 nextMarker = ListBlobs(storageAccountName, storageAccessKey, containerName, 10, null); //続きがあれば2回目の呼び出し。次の10件を取得する。 if (!string.IsNullOrEmpty(nextMarker)) { nextMarker = ListBlobs(storageAccountName, storageAccessKey, containerName, 10, nextMarker); } //続きがあれば3回目の呼び出し。次の10件を取得する。 if (!string.IsNullOrEmpty(nextMarker)) { nextMarker = ListBlobs(storageAccountName, storageAccessKey, containerName, 10, nextMarker); } } //ListBlobsWithPaging /// <summary> /// コンテナーからBLOBの一覧を取得して名前をDebug.WriteLineで出力します。 /// </summary> /// <param name="storageAccountName">対象のストレージアカウントの名前</param> /// <param name="storageAccessKey">対象のストレージアカウントのアクセスキー</param> /// <param name="containerName">対象のコンテナーの名前</param> /// <param name="maxResults">取得する最大数。最大値は5000</param> /// <param name="marker">前回の続きを取得する前回の戻り値を指定します。そうでない場合Nothing</param> /// <returns>次回続きを取得するのに使用するmarkerの値。</returns> private string ListBlobs(string storageAccountName, string storageAccessKey, string containerName, int maxResults, string marker) { //List BLOB API のURL //https://myaccount.blob.core.windows.net/mycontainer?restype=container&comp=list string url = $"https://{storageAccountName}.blob.core.windows.net/{containerName}?restype=container&comp=list&maxresults={maxResults}"; if (!string.IsNullOrEmpty(marker)) { url += "&marker=" + marker; } using (var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url)) { //必須ヘッダー string utcNow = DateTime.UtcNow.ToString("R", System.Globalization.CultureInfo.InvariantCulture); request.Headers.Add("x-ms-date", utcNow); request.Headers.Add("x-ms-version", "2020-04-08"); //すべての準備が整ったらハッシュを計算して、Authorizationヘッダーを生成します。 var authorizationHeader = GenerateAuthorizationHeader(request, storageAccessKey); request.Headers.Authorization = authorizationHeader; //▼WebAPIを呼び出します。 using (var client = new System.Net.Http.HttpClient()) { using (System.Net.Http.HttpResponseMessage response = client.SendAsync(request).GetAwaiter().GetResult()) { //成功しなかった場合例外を発生させます。 response.EnsureSuccessStatusCode(); //レスポンス本文を読み取ります。 string responseBody = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); System.Diagnostics.Debug.WriteLine("生のレスポンス全体"); System.Diagnostics.Debug.WriteLine(responseBody); //レスポンスのXMLを解析してコンテナー名を抜き出します。 System.Diagnostics.Debug.WriteLine("BLOB一覧"); System.Diagnostics.Debug.WriteLine($"{"名前",-18}{"サイズ(バイト)",14}{"作成日",17}{"最終更新日",15}"); //各数値は出力する幅です。 var document = new System.Xml.XmlDocument(); document.LoadXml(responseBody); System.Xml.XmlNodeList blobInfos = document.SelectNodes("//EnumerationResults/Blobs/Blob"); foreach (System.Xml.XmlElement blobInfo in blobInfos) { //BLOBの名前 string name = blobInfo.SelectSingleNode("Name").InnerText; //詳細情報 System.Xml.XmlElement details = (System.Xml.XmlElement)blobInfo.SelectSingleNode("Properties"); long contentLength = long.Parse(details["Content-Length"].InnerText); //サイズ(バイト) DateTime createionTime = DateTime.Parse(details["Creation-Time"].InnerText); //作成日 DateTime lastModified = DateTime.Parse(details["Last-Modified"].InnerText); //最終更新日 //各項目幅20でそろえて出力 //各項目は20文字を超えると出力位置がずれます。 //またこの書式は「いわゆる全角文字」を考慮しないため、「いわゆる全角文字」を含む場合位置がずれます。 //あくまで簡易的なものと考えてください。 System.Diagnostics.Debug.WriteLine($"{name,-20}{contentLength,20}{createionTime,20}{lastModified,20}"); } //foreach blobInfo //NextMarkerの取得 System.Xml.XmlNode nextMarkerNode = document.SelectSingleNode("//EnumerationResults/NextMarker"); string nextMarker = nextMarkerNode.InnerText; return nextMarker; } //using response } //using client } //using request } //ListBlobs |
このサンプルでは連続で10件を3回取得していますが、実際にはユーザーが「次へ」ボタンを押したタイミングや、ユーザーが一覧をスクロールして末尾に到達したときなどのタイミングで呼び出すことになると思います。
EXPLOR Azure Storage Explorer – クラウド ストレージ管理 | Microsoft Azure
AZCOPY AzCopy v10 を使用して Azure Storage にデータをコピーまたは移動する | Microsoft Docs
AUTH Azure ストレージへの要求を承認する (REST API) | Microsoft Docs
CORE Azure Storage の概要 - Azure のクラウド ストレージ | Microsoft Docs
BLOBPRICE Azure Storage Blob の価格 | Microsoft Azure
BLOB コンテナー、Blob、およびメタデータの名前付けと参照 Azure Storage | Microsoft Docs
LIBS Azure Blob Storage リファレンス | Microsoft Docs
RESTTOC Blob service REST API-Azure Storage | Microsoft Docs
SKEY 共有キーでの承認 (REST API) - Azure ストレージ | Microsoft Docs
VER Azure Storage services のバージョン管理 | Microsoft Docs
GETBLOB Blob の取得 (REST API)-Azure Storage | Microsoft Docs
PUTBLOB Put Blob (REST API)-Azure Storage | Microsoft Docs
LISTBLOBS Blob の一覧表示 (REST API)-Azure Storage | Microsoft Docs
その他の参考になるドキュメント
Azure Storage の価格 | Microsoft Azure
Azure Files の料金 | Microsoft Azure
料金 - Managed Disks | Microsoft Azure
Azure Tables Storage の料金 | Microsoft Azure
Azure Active Directory を使用した承認 (REST API)-Azure Storage | Microsoft Docs