OSS表單上傳允許網頁應用通過標準HTML表單直接將檔案上傳至OSS。本文介紹如何使用Python SDK V2產生Post簽名和Post Policy等資訊,並調用HTTP Post方法上傳檔案到OSS。
注意事項
本文範例程式碼以華東1(杭州)的地區ID
cn-hangzhou為例,預設使用外網Endpoint,如果您希望通過與OSS同地區的其他阿里雲產品訪問OSS,請使用內網Endpoint。關於OSS支援的Region與Endpoint的對應關係,請參見OSS地區和訪問網域名稱。通過表單上傳的方式上傳的Object大小不能超過5 GB。
範例程式碼
以下程式碼範例實現了表單上傳的完整過程,主要步驟如下:
建立Post Policy:定義上傳請求的有效時間和條件,包括儲存桶名稱、簽名版本、憑證資訊、請求日期和請求體長度範圍。
序列化並編碼Policy:將Policy序列化為JSON字串,並進行Base64編碼。
產生簽名密鑰:使用HMAC-SHA256演算法產生簽名密鑰,包括日期、地區、產品和請求類型。
計算簽名:使用產生的金鑰組Base64編碼後的Policy字串進行簽名,並將簽名結果轉換為十六進位字串。
構建請求體:添加對象鍵、策略、簽名版本、憑證資訊、請求日期和簽名到表單中,並將要上傳的資料寫入表單。
建立並執行請求:建立一個HTTP POST請求,佈建要求頭,並發送請求,檢查響應狀態代碼確保請求成功。
import argparse
import base64
import hashlib
import hmac
import json
import random
import requests
from datetime import datetime, timedelta
import alibabacloud_oss_v2 as oss
# 建立命令列參數解析器,用於POST對象上傳樣本。
parser = argparse.ArgumentParser(description="post object sample")
# 添加命令列參數 --region,表示儲存空間所在的地區,必需參數
parser.add_argument('--region', help='The region in which the bucket is located.', required=True)
# 添加命令列參數 --bucket,表示儲存空間的名稱,必需參數
parser.add_argument('--bucket', help='The name of the bucket.', required=True)
# 添加命令列參數 --endpoint,表示其他服務可用來訪問OSS的網域名稱,非必需參數
parser.add_argument('--endpoint', help='The domain names that other services can use to access OSS')
# 添加命令列參數 --key,表示對象的名稱,必需參數
parser.add_argument('--key', help='The name of the object.', required=True)
def main():
# 定義要上傳的內容
content = "hi oss"
product = "oss" # 產品標識符,這裡是OSS
# 解析命令列參數
args = parser.parse_args()
region = args.region # 地區資訊
bucket_name = args.bucket # 儲存桶名稱
object_name = args.key # 對象名稱
# 從環境變數中載入憑證資訊,用於身分識別驗證
credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
credential = credentials_provider.get_credentials()
access_key_id = credential.access_key_id # 存取金鑰ID
access_key_secret = credential.access_key_secret # 存取金鑰秘密
# 擷取當前UTC時間並格式化
utc_time = datetime.utcnow()
date = utc_time.strftime("%Y%m%d")
# 設定到期時間為1小時後,並建立策略(Policy)映射
expiration = utc_time + timedelta(hours=1)
policy_map = {
"expiration": expiration.strftime("%Y-%m-%dT%H:%M:%S.000Z"), # 策略到期時間
"conditions": [
{"bucket": bucket_name}, # 指定儲存桶
{"x-oss-signature-version": "OSS4-HMAC-SHA256"}, # 指定簽名版本
{"x-oss-credential": f"{access_key_id}/{date}/{region}/{product}/aliyun_v4_request"}, # 憑證資訊
{"x-oss-date": utc_time.strftime("%Y%m%dT%H%M%SZ")}, # 請求日期
["content-length-range", 1, 1024] # 內容長度範圍限制
]
}
# 將策略轉換為JSON字串,並進行Base64編碼
policy = json.dumps(policy_map)
string_to_sign = base64.b64encode(policy.encode()).decode()
def build_post_body(field_dict, boundary):
"""
構建POST請求體,將表單欄位編碼為multipart/form-data格式。
:param field_dict: 表單欄位字典
:param boundary: 分隔字元字串
:return: 編碼後的POST請求體
"""
post_body = ''
# 編碼錶單欄位,除了檔案內容和內容類型
for k, v in field_dict.items():
if k != 'content' and k != 'content-type':
post_body += '''--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n'''.format(boundary, k, v)
# 檔案內容必須是最後一個表單欄位
post_body += '''--{0}\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n{1}'''.format(
boundary, field_dict['content'])
# 添加表單欄位結束字元
post_body += '\r\n--{0}--\r\n'.format(boundary)
return post_body.encode('utf-8') # 返回UTF-8編碼的POST請求體
# 建構簽章密鑰,並使用HMAC SHA256演算法產生簽名
signing_key = "aliyun_v4" + access_key_secret
h1 = hmac.new(signing_key.encode(), date.encode(), hashlib.sha256)
h1_key = h1.digest()
h2 = hmac.new(h1_key, region.encode(), hashlib.sha256)
h2_key = h2.digest()
h3 = hmac.new(h2_key, product.encode(), hashlib.sha256)
h3_key = h3.digest()
h4 = hmac.new(h3_key, "aliyun_v4_request".encode(), hashlib.sha256)
h4_key = h4.digest()
h = hmac.new(h4_key, string_to_sign.encode(), hashlib.sha256)
signature = h.hexdigest() # 簽名結果轉換為十六進位字串
# 構建POST請求所需的表單欄位字典
field_dict = {}
field_dict['key'] = object_name
field_dict['policy'] = string_to_sign
field_dict['x-oss-signature-version'] = "OSS4-HMAC-SHA256"
field_dict['x-oss-credential'] = f"{access_key_id}/{date}/{region}/{product}/aliyun_v4_request"
field_dict['x-oss-date'] = f"{utc_time.strftime('%Y%m%dT%H%M%SZ')}"
field_dict['x-oss-signature'] = signature
field_dict['content'] = content
# 產生一個隨機字串作為表單分隔字元
boundary = ''.join(random.choice('0123456789') for _ in range(11))
# 使用build_post_body函數構建POST請求體
body = build_post_body(field_dict, boundary)
# 構造POST請求的目標URL
url = f"http://{bucket_name}.oss-{region}.aliyuncs.com"
# 設定HTTP頭部資訊,指定Content-Type為multipart/form-data,並包含邊界字串
headers = {
"Content-Type": f"multipart/form-data; boundary={boundary}",
}
# 發送POST請求到OSS
response = requests.post(url, data=body, headers=headers)
# 根據響應狀態代碼判斷上傳是否成功
if response.status_code // 100 != 2:
print(f"Post Object Fail, status code: {response.status_code}, reason: {response.reason}")
else:
print(f"post object done, status code: {response.status_code}, request id: {response.headers.get('X-Oss-Request-Id')}")
if __name__ == "__main__":
main() # 指令碼入口,當檔案被直接運行時調用main函數常見使用情境
相關文檔
關於表單上傳的完整樣本,請參見post_object.py。