BlobをREST APIでダウンロード【Azure,Python】

# はじめに
Azureの[REST API](https://docs.microsoft.com/ja-jp/rest/api/storageservices/blob-service-rest-api)でBlobをダウンロードしたのですが忘れそうなのでメモです。Pythonで実行しました。[共有キーによる認証](https://docs.microsoft.com/ja-jp/azure/storage/common/storage-rest-api-auth)でクライアント側がアクセスキーを知っている必要があるのでサーバからリクエストするときとかはこれで大丈夫かな？

# 環境
- Azureのストレージアカウントを持っている
- Python3.6(Anaconda)

HTTPリクエストをするので```pip install requests```しておきます。urllibが使える人はそっちでも大丈夫です。

# やること

雑にコードを載せます。myaccountはストレージアカウント名、mycontainerはコンテナ名、myaccess_keyはアクセスキーでそれぞれ自分のものに置き換えてください。アクセスキーは[ここを参考に](https://docs.microsoft.com/ja-jp/azure/storage/common/storage-account-keys-manage?tabs=azure-portal)確認してください。

```python
# 必要なライブラリのimport
import base64
import hashlib
import hmac
from urllib.parse import quote, unquote
import requests
import datetime

# 自分のアクセスキーで置き換える
access_key = 'myaccess_key'
# 操作したいblobのurl
blob_url = 'https://myaccount.blob.core.windows.net/mycontainer/test.png'

# 必須のヘッダー
x_ms_date = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT') #曜日, dd 月 yyyy hh:mm:ss GMT
x_ms_version = "2020-08-04"

# 改行の数などはこの通りに
# 最後の行はクエリとかがある場合はそれもいるっぽい(未確認)
StringToSign = \
    "GET\n\n\n\n\n\n\n\n\n\n\n\n"+ \
    "x-ms-date:" + x_ms_date + "\n" + \
    "x-ms-version:" + x_ms_version + "\n" + \
    "/myaccount/mycontainer/test.png" 

# 使う関数を定義
def url_quote(url):
    return quote(url)

def encode_base64(data):
    if isinstance(data, str):
        data = data.encode('utf-8')
    encoded = base64.b64encode(data)
    return encoded.decode('utf-8')

def decode_base64_to_bytes(data):
    if isinstance(data, str):
        data = data.encode('utf-8')
    return base64.b64decode(data)

def decode_base64_to_text(data):
    decoded_bytes = decode_base64_to_bytes(data)
    return decoded_bytes.decode('utf-8')

def decode_base64_to_text(data):
    decoded_bytes = decode_base64_to_bytes(data)
    return decoded_bytes.decode('utf-8')

def sign_string(key, string_to_sign, key_is_base64=True):
    if key_is_base64:
        key = decode_base64_to_bytes(key)
    else:
        if isinstance(key, str):
            key = key.encode('utf-8')
    if isinstance(string_to_sign, str):
        string_to_sign = string_to_sign.encode('utf-8')
    signed_hmac_sha256 = hmac.HMAC(key, string_to_sign, hashlib.sha256)
    digest = signed_hmac_sha256.digest()
    encoded_digest = encode_base64(digest)
    return encoded_digest

# アクセスキーとStringToSignを使って署名を作成
signature = sign_string(access_key,StringToSign)

# headerには以下の3つを入れる(PUTとかだと他にもいるかも)
headers = {
    "x-ms-date": x_ms_date,
    "x-ms-version":x_ms_version,
    "Authorization":"SharedKey myaccount:"+signature
}

# 署名を使ってREST APIをたたく
response = requests.get(blob_url,headers=headers)

# 保存用
if response.status_code >= 400:
    print(response.text)
else:
    results = response.content
    with open('./test.png', 'wb') as f:
        f.write(results)
```

あいまいなところがありすみません。
StringToSignの改行には意味があります。詳しくは**[ここ](https://docs.microsoft.com/ja-jp/azure/storage/common/storage-rest-api-auth)**をご覧ください。

リクエストヘッダーに必要なものやURL、メソッドについては**[REST APIのリファレンス](https://docs.microsoft.com/ja-jp/rest/api/storageservices/blob-service-rest-api)**を参照してください。

関数はAzure SDK for Pythonを参考にしてます([リンク](https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/__init__.py))。

# おわりに

requestsじゃなくてurllib使えるならPython標準ライブラリだけで実装できるのでAWS Lambda使うときにはLayerを追加しなくて済みます。

何か間違いがあればご指摘よろしくお願いします。

# 参考
- [REST API リファレンス](https://docs.microsoft.com/ja-jp/rest/api/storageservices/blob-service-rest-api)
- [共有キーによる承認を使用して REST API 操作を呼び出す](https://docs.microsoft.com/ja-jp/azure/storage/common/storage-rest-api-auth)
- [Azure StorageでのRest APIの署名のつくりかた（with パケットキャプチャ）](https://qiita.com/yz2cm/items/71067ccea24a948ad772)