このトピックでは、計算された署名を含む HTTP リクエストを送信して Alibaba Cloud RPC スタイルの OpenAPI 操作を呼び出す方法について説明します。
この署名バージョンは非推奨です。V3 リクエストボディと署名メカニズムの使用を推奨します。
HTTP リクエスト構造
完全な Alibaba Cloud RPC リクエストは、次の部分で構成されます。
名前 | 必須 | 説明 | 例 |
プロトコル | はい | リクエストプロトコル。API でサポートされているプロトコルは、OpenAPI メタデータで表示できます。API が | https:// |
エンドポイント | はい | サービスのエンドポイント。各 Alibaba Cloud プロダクトのサービス登録ドキュメントでエンドポイントを確認できます。 | ecs.cn-hangzhou.aliyuncs.com |
共通パラメーター | はい | すべての Alibaba Cloud API リクエストに含める必要がある共通パラメーター。詳細については、このトピックの「共通リクエストパラメーター」セクションをご参照ください。 | 操作 |
操作固有のパラメーター | いいえ | API 操作に固有のリクエストパラメーター。OpenAPI メタデータまたは OpenAPI Explorer で表示できます。 | RegionId |
HTTPMethod | はい | リクエストメソッド。API でサポートされているリクエストメソッドは、OpenAPI メタデータで表示できます。 | GET |
共通パラメーター
各 OpenAPI リクエストには、次のパラメーターを含める必要があります。
名前 | タイプ | 必須 | 説明 | 例 |
操作 | 文字列 | はい | 実行する操作。OpenAPI Explorer で実行する API 操作を検索できます。 | CreateInstance |
バージョン | 文字列 | はい | API バージョン。クラウドプロダクトの API バージョンは、Alibaba Cloud OpenAPI Developer Portal で確認できます。たとえば、Short Message Service の API バージョンは 2017-05-25 です。 | 2014-05-26 |
フォーマット | 文字列 | いいえ | 応答のフォーマット。有効な値: JSON および XML。デフォルト値: XML。 | JSON |
AccessKeyId | 文字列 | はい | Alibaba Cloud から提供された AccessKey ID。Resource Access Management (RAM) コンソールで AccessKeyId を表示できます。AccessKey ペアの作成方法の詳細については、「AccessKey ペアの作成」をご参照ください。 | yourAccessKeyId |
SignatureNonce | 文字列 | はい | 署名用の一意の乱数。リプレイ攻撃を防ぐために使用されます。リクエストごとに異なる乱数を使用することを推奨します。乱数の桁数に制限はありません。 | 15215528852396 |
タイムスタンプ | 文字列 | はい | 時刻を ISO 8601 標準の yyyy-MM-ddTHH:mm:ssZ 形式で指定します。タイムスタンプは 31 分間有効です。タイムスタンプが生成されてから 31 分以内にリクエストを送信する必要があります。たとえば、 | 2018-01-01T12:00:00Z |
SignatureMethod | 文字列 | はい | 署名メソッド。値は | HMAC-SHA1 |
SignatureVersion | 文字列 | はい | 署名アルゴリズムのバージョン。値を | 1.0 |
署名 | 文字列 | はい | 現在のリクエストの署名文字列。詳細については、「署名」をご参照ください。 | Pc5WB8gokVn0xfeu%2FZV%2BiNM1dgI%3D |
パラメーターの受け渡し
OpenAPI メタデータでは、in フィールドが各パラメーターの場所を定義し、それによってパラメーターの受け渡し方法が決まります。
パラメーターの位置 | 説明 | content-type |
"in": "query" | クエリパラメーターは、リクエスト URL の末尾にある疑問符 ( | 任意。指定する場合、値は |
"in": "formData" | フォームパラメーターの場合、パラメーターを | 必須。値は content-type=application/x-www-form-urlencoded です。 |
"in": "body" | ボディパラメーター。リクエストボディで渡されます。 | 必須。content-type の値は、リクエストのコンテンツタイプによって異なります。例:
|
リクエストパラメーターが JSON 文字列の場合、JSON 文字列内のパラメーターの順序は署名計算に影響しません。
署名メカニズム
API のセキュリティを確保するために、各リクエストは署名で認証される必要があります。次のステップでは、署名を計算する方法について説明します。
ステップ 1: 正規化されたクエリ文字列を作成する
1. 共通リクエストパラメーターと操作固有のリクエストパラメーターを、Signature 共通リクエストパラメーターを除き、パラメーターキーのアルファベット順に連結します。以下は擬似コードです。
// 共通リクエストパラメーターと操作固有のパラメーターをパラメーターキーのアルファベット順に連結します。
params = merged(publicParams,apiReuqestParams)
sortParams = sorted(params.keys())2. RFC 3986 に従って、sortParams のキーと値を UTF-8 でエンコードします。次に、等号 (=) を使用して、エンコードされた各キーとそのエンコードされた値を結合します。
エンコーディングルール:
大文字、小文字、数字、および文字
-、_、.、~はエンコードされません。その他の ASCII 文字は %XY フォーマットでエンコードする必要があります。ここで、XY は文字の ASCII コードの 16 進数の値を表します。たとえば、二重引用符 (
") は%22としてエンコードされます。次の表に、いくつかの特殊文字のエンコーディングを示します。エンコード前
エンコード後
スペース ( )
%20アスタリスク (
*)%2A%7Eチルダ (
~)
次の擬似コードはこのステップを示しています。
encodeURIComponentParam = encodeURIComponent(sortParams.key) + "=" + encodeURIComponent(sortParams.value)3. ステップ 2 のエンコードされたキーと値のペアをアンパサンド (&) で結合して、CanonicalizedQueryString を作成します。このペアは、ステップ 1 で確立されたのと同じアルファベット順で結合する必要があります。擬似コードは次のとおりです。
CanonicalizedQueryString = encodeURIComponentParam1 + "&" + encodeURIComponentParam2 + ... + encodeURIComponentParamNステップ 2: 署名対象文字列を作成する
以下の擬似コードは、署名対象の文字列 stringToSign の構築方法を示しています:
stringToSign =
HTTPMethod + "&" + // HTTPMethod は、GET などのリクエストの送信に使用される HTTP メソッドを指定します。
encodeURIComponent("/") + "&" + // encodeURIComponent は、ステップ 1 の 2 番目のステップで使用されるエンコーディングメソッドを指定します。
encodeURIComponent(CanonicalizedQueryString) // CanonicalizedQueryString は、ステップ 1 で取得された正規化されたクエリ文字列を指定します。ステップ 3: 署名を計算する
署名対象文字列 StringToSign の署名を HMAC-SHA1 署名アルゴリズムを使用して計算します。HMAC 関数のキーは、AccessKeySecret にアンパサンド (&) を追加したものです。詳細については、「RFC 2104」をご参照ください。以下は擬似コードです。
signature = Base64(HMAC_SHA1(AccessKeySecret + "&", UTF_8_Encoding_Of(stringToSign)))ここで、
Base64() は Base64 エンコーディングの関数です。
HMAC_SHA1() は HMAC-SHA1 関数です。16 進文字列ではなく、HMAC-SHA1 ハッシュの生のバイトを返します。
UTF_8_Encoding_Of() は UTF-8 エンコーディングの関数です。
ステップ 4: 署名を URL に追加する
計算された署名値をRFC 3986 に従ってエンコードし、`Signature` パラメーターとしてリクエスト URL に追加します。以下は擬似コードです。
https://service.endpoint/?sortParams.key1=sortParams.value1&sortParams.key2=sortParams.value2&...&sortParams.keyN=sortParams.valueN&Signature=signature署名コード例
固定パラメーターの例
この例では、ECS の DescribeDedicatedHosts 操作を呼び出して、1 つ以上の専用ホストの詳細をクエリします。サンプル値に基づいて、署名計算の各ステップの期待される出力を示します。これらのサンプル値を使用して実装をテストし、署名プロセスが正しいことを確認できます。
パラメーター名 | 想定されるパラメーター値 |
エンドポイント | ecs.cn-beijing.aliyuncs.com |
操作 | DescribeDedicatedHosts |
バージョン | 2014-05-26 |
フォーマット | JSON |
AccessKeyId | testid |
AccessKeySecret | testsecret |
SignatureNonce | edb2b34af0af9a6d14deaf7c1a5315eb |
タイムスタンプ | 2023-03-13T08:34:30Z |
操作固有のリクエストパラメーター
パラメーター名 | 想定されるパラメーター値 |
RegionId | cn-beijing |
署名プロセスは次のとおりです。
正規化されたクエリ文字列を作成します。
AccessKeyId=testid&Action=DescribeDedicatedHosts&Format=JSON&RegionId=cn-beijing&SignatureMethod=HMAC-SHA1&SignatureNonce=edb2b34af0af9a6d14deaf7c1a5315eb&SignatureVersion=1.0&Timestamp=2023-03-13T08%3A34%3A30Z&Version=2014-05-26署名対象文字列
stringToSignを作成します。GET&%2F&AccessKeyId%3Dtestid%26Action%3DDescribeDedicatedHosts%26Format%3DJSON%26RegionId%3Dcn-beijing%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3Dedb2b34af0af9a6d14deaf7c1a5315eb%26SignatureVersion%3D1.0%26Timestamp%3D2023-03-13T08%253A34%253A30Z%26Version%3D2014-05-26署名を計算します。次の署名は
AccessKeySecret=testsecretを使用して計算されます。9NaGiOspFP5UPcwX8Iwt2YJXXuk=完全なリクエスト URL を作成します。URL の形式は
[protocol][endpoint]?[common parameters][operation-specific parameters]です。https://ecs.cn-beijing.aliyuncs.com/?AccessKeyId=testid&Action=DescribeDedicatedHosts&Format=JSON&Signature=9NaGiOspFP5UPcwX8Iwt2YJXXuk%3D&SignatureMethod=HMAC-SHA1&SignatureNonce=edb2b34af0af9a6d14deaf7c1a5315eb&SignatureVersion=1.0&Timestamp=2023-03-13T08%3A34%3A30Z&Version=2014-05-26&RegionId=cn-beijingcURL や Wget などのツールを使用して HTTP リクエストを送信し、
DescribeDedicatedHosts操作を呼び出すことができます。
Java の例
サンプルコードは Java 8 ランタイム環境用に記述されています。特定の要件に合わせてコードを調整する必要がある場合があります。
Java の例を実行するには、次の Maven 依存関係を pom.xml ファイルに追加します。
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
public class Demo {
private static final String ACCESS_KEY_ID = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
private static final String ACCESS_KEY_SECRET = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
public static class SignatureRequest {
public final String httpMethod;
public final String host;
public final String action;
public final String version;
public final String canonicalUri = "/";
public TreeMap<String, Object> headers = new TreeMap<>();
public TreeMap<String, Object> queryParams = new TreeMap<>();
public TreeMap<String, Object> body = new TreeMap<>();
public TreeMap<String, Object> allParams = new TreeMap<>();
public byte[] bodyByte;
public SignatureRequest(String httpMethod, String host, String action, String version) {
this.httpMethod = httpMethod;
this.host = host;
this.action = action;
this.version = version;
setExtendedHeaders();
}
public void setExtendedHeaders() {
headers.put("AccessKeyId", ACCESS_KEY_ID);
headers.put("Format", "JSON");
headers.put("SignatureMethod", "HMAC-SHA1");
headers.put("SignatureVersion", "1.0");
headers.put("SignatureNonce", UUID.randomUUID().toString());
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
format.setTimeZone(new SimpleTimeZone(0, "GMT"));
headers.put("Timestamp", format.format(new Date()));
headers.put("Action", action);
headers.put("Version", version);
}
public void getAllParams() {
allParams.putAll(headers);
if (!queryParams.isEmpty()) {
allParams.putAll(queryParams);
}
if (!body.isEmpty()) {
allParams.putAll(body);
}
}
}
public static void main(String[] args) throws IOException {
// 例 1: ボディなしで API リクエストを送信します。
String httpMethod = "POST";
String endpoint = "dysmsapi.aliyuncs.com";
String action = "SendSms";
String version = "2017-05-25";
SignatureRequest signatureRequest = new SignatureRequest(httpMethod, endpoint, action, version);
signatureRequest.queryParams.put("PhoneNumbers", "123XXXXXXXX");
signatureRequest.queryParams.put("SignName", "XXXXXXX");
signatureRequest.queryParams.put("TemplateCode", "XXXXXXX");
signatureRequest.queryParams.put("TemplateParam", "XXXXXXX");
/*// 例 2: ボディ付きで API リクエストを送信します。
String httpMethod = "POST";
String endpoint = "mt.aliyuncs.com";
String action = "TranslateGeneral";
String version = "2018-10-12";
SignatureRequest signatureRequest = new SignatureRequest(httpMethod, endpoint, action, version);
TreeMap<String, Object> body = new TreeMap<>();
body.put("FormatType", "text");
body.put("SourceLanguage", "zh");
body.put("TargetLanguage", "en");
body.put("SourceText", "Hello");
body.put("Scene", "general");
signatureRequest.body = body;
String formDataToString = formDataToString(body);
signatureRequest.bodyByte = formDataToString.getBytes(StandardCharsets.UTF_8);
signatureRequest.headers.put("content-type", "application/x-www-form-urlencoded");*/
/*// 例 3: ボディがバイナリファイルの API リクエストを送信します。
String httpMethod = "POST";
String endpoint = "ocr-api.cn-hangzhou.aliyuncs.com";
String action = "RecognizeGeneral";
String version = "2021-07-07";
SignatureRequest signatureRequest = new SignatureRequest(httpMethod, endpoint, action, version);
signatureRequest.bodyByte = Files.readAllBytes(Paths.get("D:\\test.png"));
signatureRequest.headers.put("content-type", "application/octet-stream");*/
// 署名文字列を計算します。
calculateSignature(signatureRequest);
// リクエストを送信して、署名文字列が有効かどうかをテストします。
callApi(signatureRequest);
}
private static void calculateSignature(SignatureRequest signatureRequest) {
// ヘッダー、queryParam、およびボディを、正規化されたクエリ文字列の作成に使用されるマップにマージします。
signatureRequest.getAllParams();
// 正規化されたクエリ文字列を作成します。
StringBuilder canonicalQueryString = new StringBuilder();
signatureRequest.allParams.entrySet().stream().map(entry -> percentEncode(entry.getKey()) + "="
+ percentEncode(String.valueOf(entry.getValue()))).forEachOrdered(queryPart -> {
if (canonicalQueryString.length() > 0) {
canonicalQueryString.append("&");
}
canonicalQueryString.append(queryPart);
});
System.out.println("canonicalQueryString:" + canonicalQueryString);
// 署名対象文字列を作成します。
String stringToSign = signatureRequest.httpMethod + "&" + percentEncode(signatureRequest.canonicalUri) + "&" + percentEncode(String.valueOf(canonicalQueryString));
System.out.println("stringToSign:" + stringToSign);
// 署名文字列を計算します。
String signature = generateSignature(ACCESS_KEY_SECRET, stringToSign);
System.out.println("signature:" + signature);
signatureRequest.allParams.put("Signature", signature);
}
private static void callApi(SignatureRequest signatureRequest) {
try {
String url = String.format("https://%s/", signatureRequest.host);
URIBuilder uriBuilder = new URIBuilder(url);
for (Map.Entry<String, Object> entry : signatureRequest.allParams.entrySet()) {
uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue()));
}
HttpUriRequest httpRequest;
switch (signatureRequest.httpMethod) {
case "GET":
httpRequest = new HttpGet(uriBuilder.build());
break;
case "POST":
HttpPost httpPost = new HttpPost(uriBuilder.build());
if (signatureRequest.bodyByte != null) {
httpPost.setEntity(new ByteArrayEntity(signatureRequest.bodyByte, ContentType.create((String) signatureRequest.headers.get("content-type"))));
}
httpRequest = httpPost;
break;
default:
System.out.println("Unsupported HTTP method: " + signatureRequest.httpMethod);
throw new IllegalArgumentException("Unsupported HTTP method");
}
try (CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpRequest)) {
String result = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
System.out.println(result);
} catch (IOException e) {
System.out.println("Failed to send request");
throw new RuntimeException(e);
}
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
private static String formDataToString(Map<String, Object> formData) {
Map<String, Object> tileMap = new HashMap<>();
processObject(tileMap, "", formData);
StringBuilder result = new StringBuilder();
boolean first = true;
String symbol = "&";
for (Map.Entry<String, Object> entry : tileMap.entrySet()) {
String value = String.valueOf(entry.getValue());
if (value != null && !value.isEmpty()) {
if (first) {
first = false;
} else {
result.append(symbol);
}
result.append(percentEncode(entry.getKey()));
result.append("=");
result.append(percentEncode(value));
}
}
return result.toString();
}
private static void processObject(Map<String, Object> map, String key, Object value) {
// null 値に対しては、これ以上の処理は必要ありません。
if (value == null) {
return;
}
if (key == null) {
key = "";
}
// 値が List 型の場合は、リストを走査し、各要素に対して再帰を実行します。
if (value instanceof List<?>) {
List<?> list = (List<?>) value;
for (int i = 0; i < list.size(); ++i) {
processObject(map, key + "." + (i + 1), list.get(i));
}
} else if (value instanceof Map<?, ?>) {
// 値が Map 型の場合は、マップを走査し、各キーと値のペアに対して再帰を実行します。
Map<?, ?> subMap = (Map<?, ?>) value;
for (Map.Entry<?, ?> entry : subMap.entrySet()) {
processObject(map, key + "." + entry.getKey().toString(), entry.getValue());
}
} else {
// キーがピリオド (.) で始まる場合は、キーの連続性を維持するためにピリオド (.) を削除します。
if (key.startsWith(".")) {
key = key.substring(1);
}
// 値が byte[] 形式の場合は、値を UTF-8 でエンコードされた文字列に変換します。
if (value instanceof byte[]) {
map.put(key, new String((byte[]) value, StandardCharsets.UTF_8));
} else {
// 他の型の値を文字列に変換します。
map.put(key, String.valueOf(value));
}
}
}
public static String generateSignature(String accessSecret, String stringToSign) {
try {
// HMAC-SHA1 キーを作成します。
SecretKeySpec signingKey = new SecretKeySpec((accessSecret + "&").getBytes(StandardCharsets.UTF_8), "HmacSHA1");
// Mac インスタンスを作成して初期化します
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
// HMAC-SHA1 アルゴリズムを使用して署名文字列を計算します。
byte[] rawHmac = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(rawHmac);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
System.out.println("Failed to generate HMAC-SHA1 signature");
throw new RuntimeException(e);
}
}
public static String percentEncode(String str) {
if (str == null) {
throw new IllegalArgumentException("The specified string cannot be null.");
}
try {
return URLEncoder.encode(str, StandardCharsets.UTF_8.name()).replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding is not supported.", e);
}
}
}
Python の例
このサンプルコードは Python 3.12.3 用に記述されており、ご使用の環境に合わせてコードを調整する必要がある場合があります。
サンプルコードを実行するには、requests ライブラリをインストールします。
pip install requests
import base64
import hashlib
import hmac
import os
import urllib.parse
import uuid
from collections import OrderedDict
from datetime import datetime, UTC
from typing import Dict, Any
import requests
# 環境変数から AccessKey ペアを取得します。
ACCESS_KEY_ID = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_ID")
ACCESS_KEY_SECRET = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
class SignatureRequest:
"""
リクエストに署名するためのクラス。このクラスは、RPC API リクエストの構築と管理に使用されます。
"""
def __init__(self, http_method: str, host: str, action: str, version: str):
"""
署名リクエストオブジェクトを初期化します。
引数:
http_method: GET や POST などの HTTP リクエストメソッド。
host: API サービスのドメイン名。
action: API アクションの名前。
version: API のバージョン番号。
"""
self.http_method = http_method.upper()
self.host = host
self.action = action
self.version = version
self.canonical_uri = "/" # RPC API はルートパスを使用します。
self.headers: Dict[str, Any] = OrderedDict() # リクエストヘッダーパラメーター。
self.query_params: Dict[str, Any] = OrderedDict() # クエリパラメーター。
self.body: Dict[str, Any] = OrderedDict() # リクエストボディパラメーター。
self.body_byte: bytes = b"" # バイト単位のリクエストボディ。
self.all_params: Dict[str, Any] = OrderedDict() # すべてのパラメーターのコレクション。
self.set_headers()
def set_headers(self) -> None:
"""
RPC リクエストに必要な基本的なリクエストヘッダーパラメーターを設定します。
"""
self.headers["AccessKeyId"] = ACCESS_KEY_ID # AccessKey ID。
self.headers["Format"] = "JSON" # 応答のフォーマット。
self.headers["SignatureMethod"] = "HMAC-SHA1" # 署名アルゴリズム。
self.headers["SignatureVersion"] = "1.0" # 署名バージョン。
self.headers["SignatureNonce"] = "{" + str(uuid.uuid4()) + "}" # リプレイ防止用のランダムな文字列。
self.headers["Timestamp"] = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ") # タイムスタンプ。
self.headers["Action"] = self.action # API の名前。
self.headers["Version"] = self.version # API のバージョン番号。
def set_content_type(self, content_type):
self.headers["Content-Type"] = content_type
def get_all_params(self) -> None:
"""
すべてのリクエストパラメーターを収集してソートします。
"""
# すべてのパラメーターをマージします: ヘッダー、query_params、およびボディ。
self.all_params.update(self.headers)
if self.query_params:
self.all_params.update(self.query_params)
if self.body:
self.body_byte = form_data_to_string(body).encode("utf-8")
self.all_params.update(self.body)
# パラメーターを名前の ASCII 順にソートします。
self.all_params = OrderedDict(sorted(self.all_params.items()))
def calculate_signature(signature_request: SignatureRequest) -> None:
"""
RPC リクエストの署名を計算します。
引数:
signature_request: 署名リクエストオブジェクト。
"""
signature_request.get_all_params() # すべてのパラメーターを収集してソートします。
# 正規化されたクエリ文字列を作成します。
canonical_query_string = "&".join(
f"{percent_encode(k)}={percent_encode(v)}"
for k, v in signature_request.all_params.items()
)
print(f"canonicalQueryString:{canonical_query_string}")
# 署名対象文字列を作成します: HTTP メソッド + 正規 URI + 正規化されたクエリ文字列。
string_to_sign = (
f"{signature_request.http_method}&"
f"{percent_encode(signature_request.canonical_uri)}&"
f"{percent_encode(canonical_query_string)}"
)
print(f"stringToSign:{string_to_sign}")
# 署名を生成します。
signature = generate_signature(ACCESS_KEY_SECRET, string_to_sign)
signature_request.all_params["Signature"] = signature # パラメーターに署名を追加します。
def form_data_to_string(form_data: Dict[str, Any]) -> str:
"""
フォームデータを URL エンコードされた文字列に変換します。
引数:
form_data: フォームデータの辞書。
戻り値:
URL エンコードされた文字列。
"""
tile_map: Dict[str, Any] = {}
def process_object(key: str, value: Any) -> None:
"""
オブジェクトを再帰的に処理して、ネストされた構造をフラット化します。
引数:
key: パラメーターキー。
value: パラメーター値。
"""
if value is None:
return
if isinstance(value, list):
# リスト型のパラメーターを処理します。
for i, item in enumerate(value):
process_object(f"{key}.{i + 1}", item)
elif isinstance(value, dict):
# 辞書型のパラメーターを処理します。
for k, v in value.items():
process_object(f"{key}.{k}", v)
else:
# 先頭のピリオドを削除します。
clean_key = key[1:] if key.startswith(".") else key
# バイトデータと通常のデータを処理します。
tile_map[clean_key] = value.decode("utf-8") if isinstance(value, bytes) else str(value)
# すべてのフォームデータを処理します。
for k, v in form_data.items():
process_object(k, v)
# URL エンコードして項目を連結します。
encoded_items = [
f"{percent_encode(k)}={percent_encode(v)}"
for k, v in tile_map.items() if v
]
return "&".join(encoded_items)
def generate_signature(access_secret: str, string_to_sign: str) -> str:
"""
HMAC-SHA1 アルゴリズムを使用して署名を生成します。
引数:
access_secret: AccessKey Secret。
string_to_sign: 署名対象の文字列。
戻り値:
Base64 エンコードされた署名。
"""
try:
# 署名キーは、AccessKey Secret にアンパサンド (&) を追加したものです。
signing_key = (access_secret + "&").encode("utf-8")
# HMAC-SHA1 アルゴリズムを使用して署名を計算します。
signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha1).digest()
# 署名を Base64 エンコードします。
return base64.b64encode(signature).decode("utf-8")
except Exception as e:
print(f"Failed to generate HMAC-SHA1 signature: {e}")
raise
def percent_encode(s: str) -> str:
"""
RFC 3986 に基づいて文字列をパーセントエンコードします。
引数:
s: エンコードする文字列。
戻り値:
エンコードされた文字列。
"""
if s is None:
raise ValueError("Input string cannot be None")
# UTF-8 エンコーディング後に文字列を URL エンコードします。チルダ (~) 文字はエンコードされません。
encoded = urllib.parse.quote(s.encode("utf-8"), safe=b"~")
# 特殊文字のエンコーディングを置き換えます。
return encoded.replace("+", "%20").replace("*", "%2A")
def call_api(signature_request: SignatureRequest) -> None:
"""
API リクエストを開始する方法の例。
"""
url = f"https://{signature_request.host}/"
# リクエストパラメーターを作成します。
params = {k: str(v) for k, v in signature_request.all_params.items()}
# リクエストパラメーターを準備します。
request_kwargs = {
"params": params
}
# リクエストボディデータが存在する場合は追加します。
if signature_request.body_byte:
request_kwargs["data"] = signature_request.body_byte
headers = {"Content-Type": signature_request.headers.get("Content-Type")}
request_kwargs["headers"] = headers
try:
# requests.request を使用して、さまざまな HTTP メソッドを処理します。
response = requests.request(
method=signature_request.http_method,
url=url,
**request_kwargs
)
print(f"Request URL: {response.url}")
print(f"Response: {response.text}")
except requests.RequestException as e:
print(f"HTTP request failed: {e}")
raise
except Exception as e:
print(f"Failed to send request: {e}")
raise
if __name__ == "__main__":
# 例 1: ボディのないリクエスト。Content-Type ヘッダーは任意です。このヘッダーを指定する場合は、application/json に設定します。
signature_request = SignatureRequest(
http_method="POST",
host="dysmsapi.aliyuncs.com",
action="SendSms",
version="2017-05-25"
)
# query_params を使用してクエリパラメーターを設定します。
signature_request.query_params["SignName"] = "******"
signature_request.query_params["TemplateCode"] = "SMS_******"
signature_request.query_params["PhoneNumbers"] = "******"
signature_request.query_params["TemplateParam"] = "{'code':'1234'}"
# 例 2: ボディのあるリクエスト。Content-Type ヘッダーは application/x-www-form-urlencoded に設定する必要があります。application/json には設定しないでください。
"""
signature_request = SignatureRequest(
http_method="POST",
host="mt.aliyuncs.com",
action="TranslateGeneral",
version="2018-10-12"
)
body = {
"FormatType": "text",
"SourceLanguage": "zh",
"TargetLanguage": "en",
"SourceText": "Hello",
"Scene": "general"
}
signature_request.body = body
signature_request.set_content_type("application/x-www-form-urlencoded")
"""
# 例 3: バイナリファイルストリームをアップロードします。Content-Type ヘッダーは application/octet-stream に設定する必要があります。
"""
signature_request = SignatureRequest(
http_method="POST",
host="ocr-api.cn-hangzhou.aliyuncs.com",
action="RecognizeGeneral",
version="2021-07-07"
)
with open("D:\\test.jpeg", "rb") as f:
signature_request.body_byte = f.read()
signature_request.set_content_type("application/octet-stream")
"""
# 署名を計算します。
calculate_signature(signature_request)
# サンプルリクエストを開始します。
call_api(signature_request)
リファレンス
RPC スタイル API と ROA スタイル API の違いの詳細については、「API スタイル」をご参照ください。