SDK を使用せずに Alibaba Cloud OpenAPI を呼び出したい場合、またはアプリケーションのランタイム環境が SDK をサポートしていない場合は、リクエストに自己署名することで Alibaba Cloud OpenAPI を呼び出すことができます。このトピックでは、HTTP リクエストを使用して Alibaba Cloud OpenAPI を直接呼び出すのに役立つ v3 署名メカニズムについて説明します。
使用上の注意
API 呼び出しでは、v2 署名を v3 署名に直接置き換えることができます。
OpenAPI ポータルは、Alibaba Cloud プロダクトの SDK を提供します。これらのプロダクトの API は v3 署名をサポートしています。一部のクラウドプロダクトは自己管理ゲートウェイを使用しており、このトピックで説明されているものとは異なる認証メカニズムを持っていることに注意してください。これらのプロダクトに HTTP リクエストを送信する場合は、それぞれの署名メカニズムのドキュメントをご参照ください。
SLS 署名メカニズムの詳細については、「リクエスト署名」をご参照ください。
OSS 署名メカニズムの詳細については、「OSS 署名メカニズムガイド」をご参照ください。
HTTP リクエスト構造
完全な Alibaba Cloud OpenAPI リクエストは、次の部分で構成されます。
名前 | 必須 | 説明 | 例 |
プロトコル | はい | これは、さまざまなクラウドプロダクトの API リファレンスを参照して構成できます。リクエストは | https:// |
エンドポイント | はい | エンドポイント。エンドポイントがデプロイされているさまざまなエリアのエンドポイントは、各クラウドプロダクトのエンドポイントドキュメントで確認できます。 | ecs.cn-shanghai.aliyuncs.com |
resource_URI_parameters | はい | API パスと、パスおよびクエリにあるリクエストパラメーターを含む API URL です。 | ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai |
RequestHeader | はい | 共通リクエストヘッダー。通常、API バージョン、Host、および権限付与情報が含まれます。これについては後で詳しく説明します。 | Authorization: ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0 x-acs-action: RunInstances host: ecs.cn-shanghai.aliyuncs.com x-acs-date: 2023-10-26T09:01:01Z x-acs-version: 2014-05-26 x-acs-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 x-acs-signature-nonce: d410180a5abf7fe235dd9b74aca91fc0 |
RequestBody | はい | ボディで定義されたビジネスリクエストパラメーター。API メタデータから取得できます。 | |
HTTPMethod | はい | リクエストメソッド。API メタデータから取得できます。 | POST |
RequestHeader
Alibaba Cloud OpenAPI を呼び出すとき、共通リクエストヘッダーには次の情報を含める必要があります。
名前 | タイプ | 必須 | 説明 | 例 |
host | 文字列 | はい | エンドポイント。詳細については、「HTTP リクエスト構造」をご参照ください。 | ecs.cn-shanghai.aliyuncs.com |
x-acs-action | 文字列 | はい | API の名前。呼び出したい OpenAPI を検索するには、Alibaba Cloud OpenAPI Developer Portal にアクセスしてください。 | RunInstances |
x-acs-content-sha256 | 文字列 | はい | RequestBody をハッシュ化し、Base16 エンコーディングした結果。この値は HashedRequestPayload と同じです。 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
x-acs-date | 文字列 | はい | ISO 8601 形式の UTC 時刻: yyyy-MM-ddTHH:mm:ssZ。例: 2018-01-01T12:00:00Z。値は、リクエストが送信される 15 分以内の時刻である必要があります。 | 2023-10-26T10:22:32Z |
x-acs-signature-nonce | 文字列 | はい | 署名用の一意の乱数。この番号はリプレイ攻撃を防ぎます。リクエストごとに異なる乱数を使用してください。このメカニズムは HTTP プロトコルにのみ適用されます。 | 3156853299f313e23d1673dc12e1703d |
x-acs-version | 文字列 | はい | API バージョン。バージョンの取得方法の詳細については、「API バージョン (x-acs-version) を取得するにはどうすればよいですか?」をご参照ください。 | 2014-05-26 |
Authorization | 文字列 | 匿名でないリクエストに必須 | リクエストの正当性を検証するために使用される認証情報。フォーマットは Authorization: SignatureAlgorithm Credential=AccessKeyId,SignedHeaders=SignedHeaders,Signature=Signature です。 SignatureAlgorithm は署名暗号化メソッドで、ACS3-HMAC-SHA256 です。 Credential はユーザーの AccessKey ID です。RAM コンソールで AccessKey ID を表示できます。AccessKey ペアを作成するには、「AccessKey ペアの作成」をご参照ください。 SignedHeaders は、署名に含まれるリクエストヘッダーのキーを指定します。注意: セキュリティを向上させるため、Authorization を除くすべての共通リクエストヘッダーに署名してください。 Signature はリクエスト署名です。その値については、「署名メカニズム」をご参照ください。 | ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0 |
x-acs-security-token | 文字列 | STS 認証に必須 | AssumeRole 操作を呼び出して返された応答の SecurityToken の値。 |
署名メカニズム
署名は AccessKey ペアを使用して認証されます。各 HTTP または HTTPS リクエストに対して、Alibaba Cloud API Gateway はリクエストパラメーターに基づいて署名を再計算します。ゲートウェイは、この署名をリクエストで提供された署名と比較して、リクエスターの ID を検証します。このプロセスにより、データの整合性とセキュリティが保証されます。
リクエストと応答は UTF-8 文字セットでエンコードされます。
ステップ 1: 正規リクエストを作成する
次の擬似コードは、正規リクエスト (CanonicalRequest) を作成する方法を示しています:
CanonicalRequest =
HTTPRequestMethod + '\n' + // HTTP リクエストメソッド、大文字
CanonicalURI + '\n' + // 正規 URI
CanonicalQueryString + '\n' + // 正規クエリ文字列
CanonicalHeaders + '\n' + // 正規ヘッダー
SignedHeaders + '\n' + // 署名付きヘッダー
HashedRequestPayload // ハッシュ化された RequestBody の値HTTPRequestMethod (リクエストメソッド)
大文字の HTTP メソッド名 (GET や POST など)。
CanonicalURI (正規 URI)
正規 URI は、URL のエンコードされたリソースパスです。リソースパスは、URL のホストとクエリ文字列の間の部分です。ホストの後の / を含みますが、クエリ文字列の前の ? は含みません。URI の各部分 (/ で区切られた文字列) を、RFC3986 のルールに従って UTF-8 文字セットを使用してエンコードする必要があります:
文字 A-Z、a-z、0-9、および文字
-、_、.、~はエンコードされません。その他の文字は、
%の後に文字の ASCII コードを 16 進数形式で続けたものとしてエンコードされます。たとえば、半角の二重引用符 (") は%22としてエンコードされます。一部の特殊文字には特別な処理が必要なことに注意してください。エンコード前
エンコード後
スペース ( )
%20アスタリスク (
*)%2A%7Eチルダ (
~)
Java 標準ライブラリの java.net.URLEncoder を使用する場合、まず encode メソッドを使用して文字列をエンコードできます。次に、プラス記号 (+) を %20 に、アスタリスク (*) を %2A に、%7E をチルダ (~) に置き換えて、前述のルールに準拠したエンコード済み文字列を取得する必要があります。
RPC スタイルの API の場合、正規 URI としてスラッシュ (/) を使用します。
ROA スタイルの API の場合、このパラメーターは OpenAPI メタデータの path パラメーターの値です。例: /api/v1/clusters。
CanonicalQueryString (正規クエリ文字列)
API メタデータで、リクエストパラメーターがクエリ内 ("in":"query") にあると指定されている場合、次のようにすべてのパラメーターを連結する必要があります:
リクエストパラメーターを名前のアルファベット昇順でソートします。
各パラメーター名と値を、RFC3986 のルールに従って UTF-8 文字セットを使用して URI エンコードします。エンコーディングルールは、正規 URI のエンコーディングルールと同じです。
エンコードされたパラメーター名と値を等号 (
=) で接続します。パラメーターに値がない場合は、その値として空の文字列を使用します。複数のリクエストパラメーターをアンパサンド (
&) で接続します。
リクエストパラメーターが配列またはオブジェクト型の場合、パラメーター値をインデックス付きのキーと値のペアに変換する必要があります。詳細については、「配列またはオブジェクト型のパラメーターを渡すにはどうすればよいですか?」をご参照ください。
リクエストパラメーターが JSON 文字列の場合、JSON 文字列内のパラメーターの順序は署名計算に影響しません。
クエリ文字列が存在しない場合は、正規クエリ文字列として空の文字列を使用します。
例:
ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghaiHashedRequestPayload
リクエストボディをハッシュ化し、そのハッシュ値を Base16 エンコードして、ハッシュ化されたリクエストペイロードを取得します。リクエストヘッダーの x-acs-content-sha256 ヘッダーの値を、ハッシュ化されたリクエストペイロードの値に更新します。次の擬似コードは、ハッシュ化されたリクエストペイロードを計算する方法を示しています:
HashedRequestPayload = HexEncode(Hash(RequestBody))API メタデータで、API のリクエストパラメーターが
"in": "body"または"in": "formData"に設定されている場合、RequestBody でパラメーターを渡す必要があります:説明リクエストボディでリクエストパラメーターが渡されない場合、リクエストボディは空の文字列になります。
リクエストパラメーターに
"in": "formData"が含まれている場合、パラメーターをkey1=value1&key2=value2&key3=value3の形式の文字列に連結する必要があります。また、リクエストヘッダーにcontent-type=application/x-www-form-urlencodedを追加する必要があります。リクエストパラメーターが配列またはオブジェクトの場合は、パラメーター値をインデックス付きのキーと値のペアに変換する必要があります。リクエストパラメーターに
"in": "body"が含まれている場合、リクエストに `Content-Type` ヘッダーを追加する必要があります。このヘッダーの値は、リクエストのコンテンツタイプによって異なります。例:リクエストコンテンツが JSON データの場合、content-type は
application/jsonです。リクエストコンテンツがバイナリファイルストリームの場合、content-type は
application/octet-streamです。
Hash はメッセージダイジェスト関数を表します。SHA256 アルゴリズムのみがサポートされています。
HexEncode は、ダイジェストを小文字の 16 進数形式 (Base16 エンコーディング) で返すエンコーディング関数を表します。
次の例は、リクエストボディが空の場合のハッシュ化されたリクエストペイロードの値を示しています:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855CanonicalHeaders (正規リクエストヘッダー)
リクエストヘッダーのパラメーターを次のように連結します:
リクエストヘッダーをフィルタリングして、
x-acs-プレフィックスを持つか、hostまたはcontent-typeという名前のパラメーターを選択します。パラメーター名を小文字に変換し、アルファベット順にソートします。
パラメーター値の先頭と末尾のスペースをトリミングします。
パラメーター名と値をコロン (
:) で接続し、末尾に改行 (\n) を追加して、正規ヘッダーエントリを形成します。複数の正規ヘッダーエントリを 1 つの文字列に連結します。
Authorization ヘッダーを除くすべてのリクエストヘッダーは、要件を満たしている場合、署名に含める必要があります。
擬似コードは次のとおりです:
CanonicalHeaderEntry = Lowercase(HeaderName) + ':' + Trim(HeaderValue) + '\n'
CanonicalHeaders =
CanonicalHeaderEntry0 + CanonicalHeaderEntry1 + ... + CanonicalHeaderEntryN例:
host:ecs.cn-shanghai.aliyuncs.com
x-acs-action:RunInstances
x-acs-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-acs-date:2023-10-26T10:22:32Z
x-acs-signature-nonce:3156853299f313e23d1673dc12e1703d
x-acs-version:2014-05-26SignedHeaders (署名付きヘッダーのリスト)
このパラメーターは、リクエストの署名に含まれる共通リクエストヘッダーを指定します。これは、正規ヘッダーのパラメーター名に対応します。署名付きヘッダーのリストを次のように作成します:
正規ヘッダーに含まれるヘッダーの名前を小文字に変換します。
ヘッダー名をアルファベット順にソートし、セミコロン (
;) で区切ります。
擬似コードは次のとおりです:
SignedHeaders = Lowercase(HeaderName0) + ';' + Lowercase(HeaderName1) + ... + Lowercase(HeaderNameN) 例:
host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-versionステップ 2: 署名文字列を作成する
次の擬似コードに基づいて、署名文字列 (stringToSign) を作成します:
StringToSign =
SignatureAlgorithm + '\n' +
HashedCanonicalRequestSignatureAlgorithm
署名プロトコルは ACS3-HMAC-SHA256 アルゴリズムのみをサポートします。
HashedCanonicalRequest
ハッシュ化された正規リクエスト文字列。次の擬似コードは、ハッシュ化された正規リクエストを計算する方法を示しています:
HashedCanonicalRequest = HexEncode(Hash(CanonicalRequest))Hash はメッセージダイジェスト関数を表します。SHA256 アルゴリズムのみがサポートされています。
HexEncode は、ダイジェストを小文字の 16 進数形式 (Base16 エンコーディング) で返すエンコーディング関数を表します。
例:
ACS3-HMAC-SHA256
7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259ステップ 3: 署名を計算する
次の擬似コードに基づいて、署名値 (Signature) を計算します。
Signature = HexEncode(SignatureMethod(Secret, StringToSign))StringToSign: ステップ 2 で作成され、UTF-8 でエンコードされた署名文字列。
SignatureMethod: 署名アルゴリズムとして HMAC-SHA256 を使用します。
Secret: AccessKey Secret。
HexEncode: ダイジェストを小文字の 16 進数形式 (Base16 エンコーディング) で返すエンコーディング関数。
例:
06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0ステップ 4: 署名をリクエストに追加する
署名を計算した後、次の形式で Authorization リクエストヘッダーを作成します: Authorization: SignatureAlgorithm Credential=AccessKeyId,SignedHeaders=SignedHeaders,Signature=Signature。
例:
ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0署名サンプルコード
署名メカニズムをよりよく理解していただくために、このトピックでは主要なプログラミング言語での完全な実装を提供します。サンプルコードは署名メカニズムを理解するのに役立つものであり、普遍的に適用できるとは限りません。Alibaba Cloud OpenAPI は、さまざまなプログラミング言語と開発フレームワーク用の SDK を提供しています。これらの SDK を使用する場合、署名プロセスを実行する必要はありません。これにより、Alibaba Cloud に関連するアプリケーションを迅速に構築できます。SDK を使用することをお勧めします。
リクエストに署名する前に、必ず API メタデータを確認して、API のリクエストメソッド、リクエストパラメーター名、リクエストパラメーターの型、パラメーターの渡し方などの情報を取得してください。そうしないと、署名が失敗する可能性があります。
固定パラメーターの例
この例では、仮定のパラメーター値を使用して、署名メカニズムの各ステップでの正しい出力を示します。この例の仮定のパラメーター値をコードで計算に使用し、出力をこの例の内容と比較して、署名プロセスが正しいことを確認できます。
必須パラメーター名 | 仮定のパラメーター値 |
AccessKeyID | YourAccessKeyId |
AccessKeySecret | YourAccessKeySecret |
x-acs-signature-nonce | 3156853299f313e23d1673dc12e1703d |
x-acs-date | 2023-10-26T10:22:32Z |
x-acs-action | RunInstances |
x-acs-version | 2014-05-26 |
host | ecs.cn-shanghai.aliyuncs.com |
API リクエストパラメーター:
ImageId | win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd |
RegionId | cn-shanghai |
署名フローは次のとおりです:
正規リクエストを作成します。
POST
/
ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai
host:ecs.cn-shanghai.aliyuncs.com
x-acs-action:RunInstances
x-acs-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-acs-date:2023-10-26T10:22:32Z
x-acs-signature-nonce:3156853299f313e23d1673dc12e1703d
x-acs-version:2014-05-26
host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855署名文字列を作成します。
ACS3-HMAC-SHA256
7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259署名を計算します。
06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0署名をリクエストに追加します。
POST /?ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai HTTP/1.1
Authorization: ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0
x-acs-action: RunInstances
host: ecs.cn-shanghai.aliyuncs.com
x-acs-date: 2023-10-26T09:01:01Z
x-acs-version: 2014-05-26
x-acs-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-acs-signature-nonce: d410180a5abf7fe235dd9b74aca91fc0
user-agent: AlibabaCloud (Mac OS X; x86_64) Java/1.8.0_352-b08 tea-util/0.2.6 TeaDSL/1
accept: application/jsonJava の例
サンプルコードは JDK 1.8 で実行されます。特定の状況に応じてコードを調整する必要がある場合があります。
Java の例を実行するには、pom.xml ファイルに次の Maven 依存関係を追加します。
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
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 javax.xml.bind.DatatypeConverter;
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.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
public class SignatureDemo {
public static class SignatureRequest {
// HTTP メソッド
private final String httpMethod;
// リクエストパス
private final String canonicalUri;
// エンドポイント
private final String host;
// API 名
private final String xAcsAction;
// API バージョン
private final String xAcsVersion;
// ヘッダー
private final Map<String, String> headers = new TreeMap<>();
// ボディパラメーター
private byte[] body;
// クエリパラメーター
private final Map<String, Object> queryParam = new TreeMap<>();
public SignatureRequest(String httpMethod, String canonicalUri, String host,
String xAcsAction, String xAcsVersion) {
this.httpMethod = httpMethod;
this.canonicalUri = canonicalUri;
this.host = host;
this.xAcsAction = xAcsAction;
this.xAcsVersion = xAcsVersion;
initHeader();
}
private void initHeader() {
headers.put("host", host);
headers.put("x-acs-action", xAcsAction);
headers.put("x-acs-version", xAcsVersion);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
headers.put("x-acs-date", sdf.format(new Date()));
headers.put("x-acs-signature-nonce", UUID.randomUUID().toString());
}
public String getHttpMethod() {
return httpMethod;
}
public String getCanonicalUri() {
return canonicalUri;
}
public String getHost() {
return host;
}
public Map<String, String> getHeaders() {
return headers;
}
public byte[] getBody() {
return body;
}
public Map<String, Object> getQueryParam() {
return queryParam;
}
public void setBody(byte[] body) {
this.body = body;
}
public void setQueryParam(String key, Object value) {
this.queryParam.put(key, value);
}
public void setHeaders(String key, String value) {
this.headers.put(key, value);
}
}
public static class SignatureService {
private static final String ALGORITHM = "ACS3-HMAC-SHA256";
/**
* 署名を計算する
*/
public static void getAuthorization(SignatureRequest signatureRequest,
String accessKeyId, String accessKeySecret, String securityToken) {
try {
// 複雑なクエリパラメーターを処理する
Map<String, Object> processedQueryParams = new TreeMap<>();
processObject(processedQueryParams, "", signatureRequest.getQueryParam());
signatureRequest.getQueryParam().clear();
signatureRequest.getQueryParam().putAll(processedQueryParams);
// ステップ 1: 正規リクエスト文字列を作成する
String canonicalQueryString = buildCanonicalQueryString(signatureRequest.getQueryParam());
// リクエストボディのハッシュを計算する
String hashedRequestPayload = calculatePayloadHash(signatureRequest.getBody());
signatureRequest.setHeaders("x-acs-content-sha256", hashedRequestPayload);
// セキュリティトークンが存在する場合は追加する
if (securityToken != null && !securityToken.isEmpty()) {
signatureRequest.setHeaders("x-acs-security-token", securityToken);
}
// 正規ヘッダーと署名付きヘッダーを構築する
CanonicalHeadersResult canonicalHeadersResult = buildCanonicalHeaders(signatureRequest.getHeaders());
// 正規リクエストを構築する
String canonicalRequest = String.join("\n",
signatureRequest.getHttpMethod(),
signatureRequest.getCanonicalUri(),
canonicalQueryString,
canonicalHeadersResult.canonicalHeaders,
canonicalHeadersResult.signedHeaders,
hashedRequestPayload);
System.out.println("canonicalRequest=========>\n" + canonicalRequest);
// ステップ 2: 署名文字列を作成する
String hashedCanonicalRequest = sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8));
String stringToSign = ALGORITHM + "\n" + hashedCanonicalRequest;
System.out.println("stringToSign=========>\n" + stringToSign);
// ステップ 3: 署名を計算する
String signature = DatatypeConverter.printHexBinary(
hmac256(accessKeySecret.getBytes(StandardCharsets.UTF_8), stringToSign))
.toLowerCase();
System.out.println("signature=========>" + signature);
// ステップ 4: Authorization ヘッダーを構築する
String authorization = String.format("%s Credential=%s,SignedHeaders=%s,Signature=%s",
ALGORITHM, accessKeyId, canonicalHeadersResult.signedHeaders, signature);
System.out.println("authorization=========>" + authorization);
signatureRequest.getHeaders().put("Authorization", authorization);
} catch (Exception e) {
throw new RuntimeException("Failed to generate authorization", e);
}
}
/**
* formData リクエストパラメータータイプのパラメーターを処理する。
*/
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(percentCode(entry.getKey()));
result.append("=");
result.append(percentCode(value));
}
}
return result.toString();
}
/**
* 正規クエリ文字列を構築する
*/
private static String buildCanonicalQueryString(Map<String, Object> queryParams) {
return queryParams.entrySet().stream()
.map(entry -> percentCode(entry.getKey()) + "=" +
percentCode(String.valueOf(entry.getValue())))
.collect(Collectors.joining("&"));
}
/**
* リクエストボディのハッシュ値を計算する
*/
private static String calculatePayloadHash(byte[] body) throws Exception {
if (body != null) {
return sha256Hex(body);
} else {
return sha256Hex("".getBytes(StandardCharsets.UTF_8));
}
}
/**
* 正規ヘッダー情報を構築する
*/
private static CanonicalHeadersResult buildCanonicalHeaders(Map<String, String> headers) {
List<Map.Entry<String, String>> signedHeaders = headers.entrySet().stream()
.filter(entry -> {
String key = entry.getKey().toLowerCase();
return key.startsWith("x-acs-") || "host".equals(key) || "content-type".equals(key);
})
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toList());
StringBuilder canonicalHeaders = new StringBuilder();
StringBuilder signedHeadersString = new StringBuilder();
for (Map.Entry<String, String> entry : signedHeaders) {
String lowerKey = entry.getKey().toLowerCase();
String value = entry.getValue().trim();
canonicalHeaders.append(lowerKey).append(":").append(value).append("\n");
signedHeadersString.append(lowerKey).append(";");
}
if (signedHeadersString.length() > 0) {
signedHeadersString.setLength(signedHeadersString.length() - 1); // 最後のセミコロンを削除
}
return new CanonicalHeadersResult(canonicalHeaders.toString(), signedHeadersString.toString());
}
private static class CanonicalHeadersResult {
final String canonicalHeaders;
final String signedHeaders;
CanonicalHeadersResult(String canonicalHeaders, String signedHeaders) {
this.canonicalHeaders = canonicalHeaders;
this.signedHeaders = signedHeaders;
}
}
/**
* 複雑なオブジェクトパラメーターを処理する
*/
private static void processObject(Map<String, Object> map, String key, Object value) {
if (value == null) {
return;
}
if (key == null) {
key = "";
}
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<?, ?> 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);
}
if (value instanceof byte[]) {
map.put(key, new String((byte[]) value, StandardCharsets.UTF_8));
} else {
map.put(key, String.valueOf(value));
}
}
}
/**
* HMAC-SHA256 計算
*/
private static byte[] hmac256(byte[] secretKey, String str) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, mac.getAlgorithm());
mac.init(secretKeySpec);
return mac.doFinal(str.getBytes(StandardCharsets.UTF_8));
}
/**
* SHA-256 ハッシュ計算
*/
private static String sha256Hex(byte[] input) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(input);
return DatatypeConverter.printHexBinary(digest).toLowerCase();
}
/**
* URL エンコーディング
*/
public static String percentCode(String str) {
if (str == null) {
return "";
}
try {
return URLEncoder.encode(str, "UTF-8")
.replace("+", "%20")
.replace("*", "%2A")
.replace("%7E", "~");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported", e);
}
}
}
/**
* 署名の例。必要に応じて main メソッドの例のパラメーターを置き換えてください。
* canonicalUri 値を取得するロジックは、ROA API と RPC API の唯一の違いです。残りは同様です。
* <p>
* API メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得し、パラメーターを SignatureRequest にカプセル化します。
* 1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、queryParam を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
* 2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/octet-stream または application/json です。注意: RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
* 3. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/x-www-form-urlencoded です。
*/
public static void main(String[] args) throws IOException {
// 環境変数から AccessKey を取得
String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
String securityToken = System.getenv("ALIBABA_CLOUD_SECURITY_TOKEN");
if (accessKeyId == null || accessKeySecret == null) {
System.err.println("Set the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables.");
return;
}
// RPC API の例 1: リクエストパラメーターは "query" にあります。この例では、ECS の DescribeInstanceStatus を使用します。
SignatureRequest signatureRequest = new SignatureRequest(
"POST",
"/",
"ecs.cn-hangzhou.aliyuncs.com",
"DescribeInstanceStatus",
"2014-05-26"
);
signatureRequest.setQueryParam("RegionId", "cn-hangzhou");
signatureRequest.setQueryParam("InstanceId", Arrays.asList("i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"));
/*// RPC API の例 2: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)。この例では、OCR の RecognizeGeneral を使用します。
SignatureRequest signatureRequest = new SignatureRequest(
"POST",
"/",
"ocr-api.cn-hangzhou.aliyuncs.com",
"RecognizeGeneral",
"2021-07-07");
signatureRequest.setBody(Files.readAllBytes(Paths.get("D:\\test.jpeg")));
signatureRequest.setHeaders("content-type", "application/octet-stream");*/
/*// RPC API の例 3: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)。この例では、Machine Translation の TranslateGeneral を使用します。
String httpMethod = "POST";
String canonicalUri = "/";
String host = "mt.aliyuncs.com";
String xAcsAction = "TranslateGeneral";
String xAcsVersion = "2018-10-12";
SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
Map<String, Object> body = new HashMap<>();
body.put("FormatType", "text");
body.put("SourceLanguage", "zh");
body.put("TargetLanguage", "en");
body.put("SourceText", "Hello");
body.put("Scene", "general");
String formDataToString = SignatureService.formDataToString(body);
signatureRequest.setBody(formDataToString.getBytes(StandardCharsets.UTF_8));
signatureRequest.setHeaders("content-type", "application/x-www-form-urlencoded");*/
/*// ROA API POST リクエストの例。この例では、Container Service でクラスターを作成します。
SignatureRequest signatureRequest = new SignatureRequest(
"POST",
"/clusters",
"cs.cn-chengdu.aliyuncs.com",
"CreateCluster",
"2015-12-15");
TreeMap<String, Object> body = new TreeMap<>();
body.put("name", "test");
body.put("cluster_type", "ManagedKubernetes");
body.put("kubernetes_version", "1.34.1-aliyun.1");
body.put("region_id", "cn-chengdu");
body.put("snat_entry", true);
body.put("deletion_protection", true);
body.put("proxy_mode", "ipvs");
body.put("profile", "Default");
body.put("timezone", "Asia/Shanghai");
body.put("cluster_spec", "ack.pro.small");
body.put("enable_rrsa", false);
body.put("service_cidr", "192.168.0.0/16");
body.put("zone_ids", Arrays.asList("cn-chengdu-b","cn-chengdu-b"));
Gson gson = (new GsonBuilder()).disableHtmlEscaping().create();
signatureRequest.setBody(gson.toJson(body).getBytes(StandardCharsets.UTF_8));
signatureRequest.setHeaders("content-type", "application/json");*/
/*// ROA API GET リクエスト。この例では、Container Service のクラスター情報をクエリします。
SignatureRequest signatureRequest = new SignatureRequest(
"GET",
"/clusters/" + SignatureService.percentCode("c299f90b63b************") + "/resources",
"cs.cn-chengdu.aliyuncs.com",
"DescribeClusterResources",
"2015-12-15");
signatureRequest.setQueryParam("with_addon_resources", true);*/
/*// ROA API DELETE リクエスト。この例では、クラスターを削除します。
SignatureRequest signatureRequest = new SignatureRequest(
"DELETE",
"/clusters/" + SignatureService.percentCode("c299f90b63b************"),
"cs.cn-chengdu.aliyuncs.com",
"DeleteCluster",
"2015-12-15");*/
// 署名を生成する
SignatureService.getAuthorization(signatureRequest, accessKeyId, accessKeySecret, securityToken);
// API が正常に呼び出せるかテストする
callApi(signatureRequest);
}
/**
* テスト専用
*/
private static void callApi(SignatureRequest signatureRequest) {
try {
String url = "https://" + signatureRequest.getHost() + signatureRequest.getCanonicalUri();
URIBuilder uriBuilder = new URIBuilder(url);
// クエリパラメーターを追加する
for (Map.Entry<String, Object> entry : signatureRequest.getQueryParam().entrySet()) {
uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue()));
}
HttpUriRequest httpRequest;
switch (signatureRequest.getHttpMethod()) {
case "GET":
httpRequest = new HttpGet(uriBuilder.build());
break;
case "POST":
HttpPost httpPost = new HttpPost(uriBuilder.build());
if (signatureRequest.getBody() != null) {
httpPost.setEntity(new ByteArrayEntity(signatureRequest.getBody(), ContentType.create(signatureRequest.getHeaders().get("content-type"))));
}
httpRequest = httpPost;
break;
case "DELETE":
httpRequest = new HttpDelete(uriBuilder.build());
break;
default:
System.out.println("Unsupported HTTP method: " + signatureRequest.getHttpMethod());
throw new IllegalArgumentException("Unsupported HTTP method");
}
// リクエストヘッダーを追加する
for (Map.Entry<String, String> entry : signatureRequest.getHeaders().entrySet()) {
httpRequest.addHeader(entry.getKey(), entry.getValue());
}
// リクエストを送信する
try (CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = httpClient.execute(httpRequest)) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println("API Response: " + result);
}
} catch (IOException | URISyntaxException e) {
throw new RuntimeException("Failed to call API", e);
}
}
}
Python の例
サンプルコードは Python 3.12.3 で実行されます。特定の状況に応じてコードを調整する必要がある場合があります。
pytz と requests を手動でインストールする必要があります。Python のバージョンに応じて、ターミナルで次のコマンドを実行します。
Python 3
pip3 install pytz
pip3 install requestsimport hashlib
import hmac
import json
import os
import uuid
from collections import OrderedDict
from datetime import datetime
from typing import Any, Dict, List, Optional, Union
from urllib.parse import quote_plus, urlencode
import pytz
import requests
class SignatureRequest:
def __init__(
self,
http_method: str,
canonical_uri: str,
host: str,
x_acs_action: str,
x_acs_version: str
):
self.http_method = http_method
self.canonical_uri = canonical_uri
self.host = host
self.x_acs_action = x_acs_action
self.x_acs_version = x_acs_version
self.headers = self._init_headers()
self.query_param = OrderedDict() # type: Dict[str, Any]
self.body = None # type: Optional[bytes]
def _init_headers(self) -> Dict[str, str]:
current_time = datetime.now(pytz.timezone('Etc/GMT'))
headers = OrderedDict([
('host', self.host),
('x-acs-action', self.x_acs_action),
('x-acs-version', self.x_acs_version),
('x-acs-date', current_time.strftime('%Y-%m-%dT%H:%M:%SZ')),
('x-acs-signature-nonce', str(uuid.uuid4())),
])
return headers
def sorted_query_params(self) -> None:
"""クエリパラメーターを名前でソートし、エンコードされた文字列を返します。"""
self.query_param = dict(sorted(self.query_param.items()))
def sorted_headers(self) -> None:
"""リクエストヘッダーを名前でソートし、エンコードされた文字列を返します。"""
self.headers = dict(sorted(self.headers.items()))
def get_authorization(request: SignatureRequest) -> None:
try:
new_query_param = OrderedDict()
process_object(new_query_param, '', request.query_param)
request.query_param.clear()
request.query_param.update(new_query_param)
request.sorted_query_params()
# ステップ 1: 正規リクエスト文字列を作成する
canonical_query_string = "&".join(
f"{percent_code(quote_plus(k))}={percent_code(quote_plus(str(v)))}"
for k, v in request.query_param.items()
)
hashed_request_payload = sha256_hex(request.body or b'')
request.headers['x-acs-content-sha256'] = hashed_request_payload
if SECURITY_TOKEN:
signature_request.headers["x-acs-security-token"] = SECURITY_TOKEN
request.sorted_headers()
filtered_headers = OrderedDict()
for k, v in request.headers.items():
if k.lower().startswith("x-acs-") or k.lower() in ["host", "content-type"]:
filtered_headers[k.lower()] = v
canonical_headers = "\n".join(f"{k}:{v}" for k, v in filtered_headers.items()) + "\n"
signed_headers = ";".join(filtered_headers.keys())
canonical_request = (
f"{request.http_method}\n{request.canonical_uri}\n{canonical_query_string}\n"
f"{canonical_headers}\n{signed_headers}\n{hashed_request_payload}"
)
print(canonical_request)
# ステップ 2: 署名文字列を作成する
hashed_canonical_request = sha256_hex(canonical_request.encode("utf-8"))
string_to_sign = f"{ALGORITHM}\n{hashed_canonical_request}"
print(string_to_sign)
# ステップ 3: 署名を計算する
signature = hmac256(ACCESS_KEY_SECRET.encode("utf-8"), string_to_sign).hex().lower()
# ステップ 4: Authorization ヘッダーを構築する
authorization = f'{ALGORITHM} Credential={ACCESS_KEY_ID},SignedHeaders={signed_headers},Signature={signature}'
request.headers["Authorization"] = authorization
except Exception as e:
print("Failed to get authorization")
print(e)
def form_data_to_string(form_data: Dict[str, Any]) -> str:
tile_map = OrderedDict()
process_object(tile_map, "", form_data)
return urlencode(tile_map)
def process_object(result_map: Dict[str, str], key: str, value: Any) -> None:
if value is None:
return
if isinstance(value, (list, tuple)):
for i, item in enumerate(value):
process_object(result_map, f"{key}.{i + 1}", item)
elif isinstance(value, dict):
for sub_key, sub_value in value.items():
process_object(result_map, f"{key}.{sub_key}", sub_value)
else:
key = key.lstrip(".")
result_map[key] = value.decode("utf-8") if isinstance(value, bytes) else str(value)
def hmac256(key: bytes, msg: str) -> bytes:
return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
def sha256_hex(s: bytes) -> str:
return hashlib.sha256(s).hexdigest()
def call_api(request: SignatureRequest) -> None:
url = f"https://{request.host}{request.canonical_uri}"
if request.query_param:
url += "?" + urlencode(request.query_param, doseq=True, safe="*")
headers = dict(request.headers)
data = request.body
try:
response = requests.request(
method=request.http_method, url=url, headers=headers, data=data
)
response.raise_for_status()
print(response.text)
except requests.RequestException as e:
print("Failed to send request")
print(e)
def percent_code(encoded_str: str) -> str:
return encoded_str.replace("+", "%20").replace("*", "%2A").replace("%7E", "~")
# 環境変数から Access Key ID と Access Key Secret を取得
ACCESS_KEY_ID = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_ID")
ACCESS_KEY_SECRET = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
SECURITY_TOKEN = os.environ.get("ALIBABA_CLOUD_SECURITY_TOKEN")
ALGORITHM = "ACS3-HMAC-SHA256"
"""
署名の例。テスト時には、main 関数から例を選択し、必要に応じて例の値を変更できます。たとえば、SendSms を呼び出すには、例 1 を選択し、http_method、host、x_acs_action、x_acs_version、および query_param を変更します。
canonicalUri 値を取得するロジックは、ROA API と RPC API の唯一の違いです。
OpenAPI メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得し、パラメーターを SignatureRequest にカプセル化します。
1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、content-type を設定せずに queryParam を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡し、必要に応じて content-type を設定します。注意: RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
3. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、content-type を application/x-www-form-urlencoded としてボディ経由でパラメーターを渡します。
"""
if __name__ == "__main__":
# RPC API リクエストの例 1: リクエストパラメーターは "query" にあります
http_method = "POST" # リクエストメソッド。メタデータから取得できます。POST を推奨します。
canonical_uri = "/" # RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
host = "ecs.cn-hangzhou.aliyuncs.com" # クラウドプロダクトのエンドポイント
x_acs_action = "DescribeInstanceStatus" # API 名
x_acs_version = "2014-05-26" # API バージョン番号
signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
# DescribeInstanceStatus リクエストパラメーター:
# RegionId はメタデータで String 型、"in":"query"、必須
signature_request.query_param['RegionId'] = 'cn-hangzhou'
# InstanceId はメタデータで array 型、"in":"query"、必須ではない
signature_request.query_param['InstanceId'] = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX",
"i-bp1incuofvzxXXXXXXXX"]
# # RPC API リクエストの例 2: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)
# http_method = "POST"
# canonical_uri = "/"
# host = "ocr-api.cn-hangzhou.aliyuncs.com"
# x_acs_action = "RecognizeGeneral"
# x_acs_version = "2021-07-07"
# signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
# # リクエストパラメーターはメタデータで "in": "body" と表示され、ボディ経由で渡されます。
# file_path = "D:\\test.png"
# with open(file_path, 'rb') as file:
# # イメージコンテンツをバイト配列として読み取る
# signature_request.body = file.read()
# signature_request.headers["content-type"] = "application/octet-stream"
# # RPC API リクエストの例 3: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)
# http_method = "POST"
# canonical_uri = "/"
# host = "mt.aliyuncs.com"
# x_acs_action = "TranslateGeneral"
# x_acs_version = "2018-10-12"
# signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
# # TranslateGeneral リクエストパラメーター:
# # Context はメタデータで String 型、"in":"query"、必須ではない
# signature_request.query_param['Context'] = 'Morning'
# # FormatType、SourceLanguage、TargetLanguage などのパラメーターはメタデータで "in":"formData" と表示されます
# form_data = OrderedDict()
# form_data["FormatType"] = "text"
# form_data["SourceLanguage"] = "zh"
# form_data["TargetLanguage"] = "en"
# form_data["SourceText"] = "Hello"
# form_data["Scene"] = "general"
# signature_request.body = bytes(form_data_to_string(form_data), 'utf-8')
# signature_request.headers["content-type"] = "application/x-www-form-urlencoded"
# # 例 4: ROA API POST リクエスト
# http_method = "POST"
# canonical_uri = "/clusters"
# host = "cs.cn-beijing.aliyuncs.com"
# x_acs_action = "CreateCluster"
# x_acs_version = "2015-12-15"
# signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
# リクエストパラメーターはメタデータで "in":"body" と表示され、ボディ経由で渡されます。
# body = OrderedDict()
# body["name"] = "testDemo"
# body["region_id"] = "cn-beijing"
# body["cluster_type"] = "ExternalKubernetes"
# body["vpcid"] = "vpc-2zeou1uod4ylaXXXXXXXX"
# body["container_cidr"] = "172.16.1.0/20"
# body["service_cidr"] = "10.2.0.0/24"
# body["security_group_id"] = "sg-2ze1a0rlgeo7XXXXXXXX"
# body["vswitch_ids"] = ["vsw-2zei30dhfldu8XXXXXXXX"]
# signature_request.body = bytes(json.dumps(body, separators=(',', ':')), 'utf-8')
# signature_request.headers["content-type"] = "application/json; charset=utf-8"
# # 例 5: ROA API GET リクエスト
# http_method = "GET"
# # canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: percent_code({path_parameter})
# cluster_id_encode = percent_code("ca72cfced86db497cab79aa28XXXXXXXX")
# canonical_uri = f"/clusters/{cluster_id_encode}/resources"
# host = "cs.cn-beijing.aliyuncs.com"
# x_acs_action = "DescribeClusterResources"
# x_acs_version = "2015-12-15"
# signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
# signature_request.query_param['with_addon_resources'] = True
# # 例 6: ROA API DELETE リクエスト
# http_method = "DELETE"
# # canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: percent_code({path_parameter})
# cluster_id_encode = percent_code("ca72cfced86db497cab79aa28XXXXXXXX")
# canonical_uri = f"/clusters/{cluster_id_encode}"
# host = "cs.cn-beijing.aliyuncs.com"
# x_acs_action = "DeleteCluster"
# x_acs_version = "2015-12-15"
# signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
get_authorization(signature_request)
call_api(signature_request)
Go の例
サンプルコードは go1.22.2 で実行されます。特定の状況に応じてコードを調整する必要がある場合があります。
ターミナルで次のコマンドを実行します:
go get github.com/google/uuid
go get golang.org/x/exp/mapspackage main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"os"
"sort"
"golang.org/x/exp/maps"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/google/uuid"
)
type Request struct {
httpMethod string
canonicalUri string
host string
xAcsAction string
xAcsVersion string
headers map[string]string
body []byte
queryParam map[string]interface{}
}
func NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion string) *Request {
req := &Request{
httpMethod: httpMethod,
canonicalUri: canonicalUri,
host: host,
xAcsAction: xAcsAction,
xAcsVersion: xAcsVersion,
headers: make(map[string]string),
queryParam: make(map[string]interface{}),
}
req.headers["host"] = host
req.headers["x-acs-action"] = xAcsAction
req.headers["x-acs-version"] = xAcsVersion
req.headers["x-acs-date"] = time.Now().UTC().Format(time.RFC3339)
req.headers["x-acs-signature-nonce"] = uuid.New().String()
return req
}
// os.Getenv() は環境変数から AccessKey ID と AccessKey Secret を取得します。
var (
AccessKeyId = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
AccessKeySecret = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
SecurityToken = os.Getenv("ALIBABA_CLOUD_SECURITY_TOKEN")
ALGORITHM = "ACS3-HMAC-SHA256"
)
// 署名の例。必要に応じて main メソッドの例のパラメーターを置き換えてください。
// canonicalUri 値を取得するロジックは、ROA API と RPC API の唯一の違いです。残りは同様です。
// API メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得し、パラメーターを SignatureRequest にカプセル化します。
// 1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、queryParam を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
// 2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/octet-stream または application/json です。RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
// 3. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/x-www-form-urlencoded です。
func main() {
// RPC API リクエストの例 1: リクエストパラメーターは "query" にあります
httpMethod := "POST" // リクエストメソッド。ほとんどの RPC API は POST と GET の両方をサポートしています。この例では POST を使用します。
canonicalUri := "/" // RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
host := "ecs.cn-hangzhou.aliyuncs.com" // クラウドプロダクトのエンドポイント
xAcsAction := "DescribeInstanceStatus" // API 名
xAcsVersion := "2014-05-26" // API バージョン番号
req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
// DescribeInstanceStatus リクエストパラメーター:
// RegionId はメタデータで String 型、"in":"query"、必須
req.queryParam["RegionId"] = "cn-hangzhou"
// InstanceId はメタデータで array 型、"in":"query"、必須ではない
instanceIds := []interface{}{"i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"}
req.queryParam["InstanceId"] = instanceIds
// // RPC API リクエストの例 2: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)
// httpMethod := "POST"
// canonicalUri := "/"
// host := "ocr-api.cn-hangzhou.aliyuncs.com"
// xAcsAction := "RecognizeGeneral"
// xAcsVersion := "2021-07-07"
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
// // ファイルコンテンツを読み取る
// filePath := "D:\\test.png"
// bytes, err := os.ReadFile(filePath)
// if err != nil {
// fmt.Println("Error reading file:", err)
// return
// }
// req.body = bytes
// req.headers["content-type"] = "application/octet-stream"
// // RPC API リクエストの例 3: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)
// httpMethod := "POST"
// canonicalUri := "/"
// host := "mt.aliyuncs.com"
// xAcsAction := "TranslateGeneral"
// xAcsVersion := "2018-10-12"
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
// // TranslateGeneral リクエストパラメーター:
// // Context はメタデータで String 型、"in":"query"、必須ではない
// req.queryParam["Context"] = "Morning"
// // FormatType、SourceLanguage、TargetLanguage などのパラメーターはメタデータで "in":"formData" と表示されます
// body := make(map[string]interface{})
// body["FormatType"] = "text"
// body["SourceLanguage"] = "zh"
// body["TargetLanguage"] = "en"
// body["SourceText"] = "Hello"
// body["Scene"] = "general"
// str := formDataToString(body)
// req.body = []byte(*str)
// req.headers["content-type"] = "application/x-www-form-urlencoded"
// // ROA API POST リクエスト
// httpMethod := "POST"
// canonicalUri := "/clusters"
// host := "cs.cn-beijing.aliyuncs.com"
// xAcsAction := "CreateCluster"
// xAcsVersion := "2015-12-15"
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
// // リクエストパラメーターをカプセル化します。リクエストパラメーターはメタデータで "in": "body" と表示され、パラメーターがボディにあることを意味します。
// body := make(map[string]interface{})
// body["name"] = "testDemo"
// body["region_id"] = "cn-beijing"
// body["cluster_type"] = "ExternalKubernetes"
// body["vpcid"] = "vpc-2zeou1uod4ylaXXXXXXXX"
// body["container_cidr"] = "10.0.0.0/8"
// body["service_cidr"] = "172.16.1.0/20"
// body["security_group_id"] = "sg-2ze1a0rlgeo7XXXXXXXX"
// vswitch_ids := []interface{}{"vsw-2zei30dhfldu8XXXXXXXX"}
// body["vswitch_ids"] = vswitch_ids
// jsonBytes, err := json.Marshal(body)
// if err != nil {
// fmt.Println("Error marshaling to JSON:", err)
// return
// }
// req.body = []byte(jsonBytes)
// req.headers["content-type"] = "application/json; charset=utf-8"
// // ROA API GET リクエスト
// httpMethod := "GET"
// // canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: percentCode({path_parameter})
// canonicalUri := "/clusters/" + percentCode("c558c166928f9446dae400d106e124f66") + "/resources"
// host := "cs.cn-beijing.aliyuncs.com"
// xAcsAction := "DescribeClusterResources"
// xAcsVersion := "2015-12-15"
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
// req.queryParam["with_addon_resources"] = "true"
// // ROA API DELETE リクエスト
// httpMethod := "DELETE"
// // canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: percentCode({path_parameter})
// canonicalUri := "/clusters/" + percentCode("c558c166928f9446dae400d106e124f66")
// host := "cs.cn-beijing.aliyuncs.com"
// xAcsAction := "DeleteCluster"
// xAcsVersion := "2015-12-15"
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
// 署名プロセス
getAuthorization(req)
// API を呼び出す
error := callAPI(req)
if error != nil {
println(error.Error())
}
}
func callAPI(req *Request) error {
urlStr := "https://" + req.host + req.canonicalUri
q := url.Values{}
keys := maps.Keys(req.queryParam)
sort.Strings(keys)
for _, k := range keys {
v := req.queryParam[k]
q.Set(k, fmt.Sprintf("%v", v))
}
urlStr += "?" + q.Encode()
fmt.Println(urlStr)
httpReq, err := http.NewRequest(req.httpMethod, urlStr, strings.NewReader(string(req.body)))
if err != nil {
return err
}
for key, value := range req.headers {
httpReq.Header.Set(key, value)
}
client := &http.Client{}
resp, err := client.Do(httpReq)
if err != nil {
return err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
var respBuffer bytes.Buffer
_, err = io.Copy(&respBuffer, resp.Body)
if err != nil {
return err
}
respBytes := respBuffer.Bytes()
fmt.Println(string(respBytes))
return nil
}
func getAuthorization(req *Request) {
// queryParam の List および Map 型のパラメーターを処理し、パラメーターをフラット化する
newQueryParams := make(map[string]interface{})
processObject(newQueryParams, "", req.queryParam)
req.queryParam = newQueryParams
// ステップ 1: 正規リクエスト文字列を作成する
canonicalQueryString := ""
keys := maps.Keys(req.queryParam)
sort.Strings(keys)
for _, k := range keys {
v := req.queryParam[k]
canonicalQueryString += percentCode(url.QueryEscape(k)) + "=" + percentCode(url.QueryEscape(fmt.Sprintf("%v", v))) + "&"
}
canonicalQueryString = strings.TrimSuffix(canonicalQueryString, "&")
fmt.Printf("canonicalQueryString========>%s\n", canonicalQueryString)
var bodyContent []byte
if req.body == nil {
bodyContent = []byte("")
} else {
bodyContent = req.body
}
hashedRequestPayload := sha256Hex(bodyContent)
req.headers["x-acs-content-sha256"] = hashedRequestPayload
if SecurityToken != "" {
req.headers["x-acs-security-token"] = SecurityToken
}
canonicalHeaders := ""
signedHeaders := ""
HeadersKeys := maps.Keys(req.headers)
sort.Strings(HeadersKeys)
for _, k := range HeadersKeys {
lowerKey := strings.ToLower(k)
if lowerKey == "host" || strings.HasPrefix(lowerKey, "x-acs-") || lowerKey == "content-type" {
canonicalHeaders += lowerKey + ":" + req.headers[k] + "\n"
signedHeaders += lowerKey + ";"
}
}
signedHeaders = strings.TrimSuffix(signedHeaders, ";")
canonicalRequest := req.httpMethod + "\n" + req.canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload
fmt.Printf("canonicalRequest========>\n%s\n", canonicalRequest)
// ステップ 2: 署名文字列を作成する
hashedCanonicalRequest := sha256Hex([]byte(canonicalRequest))
stringToSign := ALGORITHM + "\n" + hashedCanonicalRequest
fmt.Printf("stringToSign========>\n%s\n", stringToSign)
// ステップ 3: 署名を計算する
byteData, err := hmac256([]byte(AccessKeySecret), stringToSign)
if err != nil {
fmt.Println(err)
panic(err)
}
signature := strings.ToLower(hex.EncodeToString(byteData))
// ステップ 4: Authorization ヘッダーを構築する
authorization := ALGORITHM + " Credential=" + AccessKeyId + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature
req.headers["Authorization"] = authorization
}
func hmac256(key []byte, toSignString string) ([]byte, error) {
// HMAC-SHA256 ハッシュをインスタンス化する
h := hmac.New(sha256.New, key)
// 署名する文字列を書き込む
_, err := h.Write([]byte(toSignString))
if err != nil {
return nil, err
}
// 署名を計算して返す
return h.Sum(nil), nil
}
func sha256Hex(byteArray []byte) string {
// SHA-256 ハッシュ関数をインスタンス化する
hash := sha256.New()
// 文字列をハッシュ関数に書き込む
_, _ = hash.Write(byteArray)
// SHA-256 ハッシュ値を計算し、小文字の 16 進数文字列に変換する
hexString := hex.EncodeToString(hash.Sum(nil))
return hexString
}
func percentCode(str string) string {
// 特定のエンコードされた文字を置き換える
str = strings.ReplaceAll(str, "+", "%20")
str = strings.ReplaceAll(str, "*", "%2A")
str = strings.ReplaceAll(str, "%7E", "~")
return str
}
func formDataToString(formData map[string]interface{}) *string {
tmp := make(map[string]interface{})
processObject(tmp, "", formData)
res := ""
urlEncoder := url.Values{}
for key, value := range tmp {
v := fmt.Sprintf("%v", value)
urlEncoder.Add(key, v)
}
res = urlEncoder.Encode()
return &res
}
// processObject はオブジェクトを再帰的に処理し、複雑なオブジェクト (Map や List など) をフラットなキーと値のペアに展開します
func processObject(mapResult map[string]interface{}, key string, value interface{}) {
if value == nil {
return
}
switch v := value.(type) {
case []interface{}:
for i, item := range v {
processObject(mapResult, fmt.Sprintf("%s.%d", key, i+1), item)
}
case map[string]interface{}:
for subKey, subValue := range v {
processObject(mapResult, fmt.Sprintf("%s.%s", key, subKey), subValue)
}
default:
if strings.HasPrefix(key, ".") {
key = key[1:]
}
if b, ok := v.([]byte); ok {
mapResult[key] = string(b)
} else {
mapResult[key] = fmt.Sprintf("%v", v)
}
}
}
Node.js の例
サンプルコードは Node.js v20.13.1 で実行されます。特定の状況に応じてコードを調整する必要がある場合があります。
この例では Node.js を使用します。
const crypto = require('crypto');
const fs = require('fs');
class Request {
constructor(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion) {
this.httpMethod = httpMethod;
this.canonicalUri = canonicalUri || '/';
this.host = host;
this.xAcsAction = xAcsAction;
this.xAcsVersion = xAcsVersion;
this.headers = {};
this.body = null;
this.queryParam = {};
this.initHeader();
}
initHeader() {
const date = new Date();
this.headers = {
'host': this.host,
'x-acs-action': this.xAcsAction,
'x-acs-version': this.xAcsVersion,
'x-acs-date': date.toISOString().replace(/\..+/, 'Z'),
'x-acs-signature-nonce': crypto.randomBytes(16).toString('hex')
}
}
}
const ALGORITHM = 'ACS3-HMAC-SHA256';
const accessKeyId = process.env.ALIBABA_CLOUD_ACCESS_KEY_ID;
const accessKeySecret = process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET;
const securityToken = process.env.ALIBABA_CLOUD_SECURITY_TOKEN;
const encoder = new TextEncoder()
if (!accessKeyId || !accessKeySecret) {
console.error('ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables must be set.');
process.exit(1);
}
function getAuthorization(signRequest) {
try {
newQueryParam = {};
processObject(newQueryParam, "", signRequest.queryParam);
signRequest.queryParam = newQueryParam;
// ステップ 1: 正規リクエスト文字列を作成する
const canonicalQueryString = Object.entries(signRequest.queryParam)
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, value]) => `${percentCode(key)}=${percentCode(value)}`)
.join('&');
// リクエストボディ。リクエストボディが空の場合 (GET リクエストなど)、RequestPayload は固定の空文字列です。
const requestPayload = signRequest.body || encoder.encode('');
const hashedRequestPayload = sha256Hex(requestPayload);
signRequest.headers['x-acs-content-sha256'] = hashedRequestPayload;
if (securityToken) {
signRequest.headers['x-acs-security-token'] = securityToken;
}
// すべてのキーを小文字に変換する
signRequest.headers = Object.fromEntries(
Object.entries(signRequest.headers).map(([key, value]) => [key.toLowerCase(), value])
);
const sortedKeys = Object.keys(signRequest.headers)
.filter(key => key.startsWith('x-acs-') || key === 'host' || key === 'content-type')
.sort();
// 署名付きヘッダーのリスト。複数のリクエストヘッダー名 (小文字) がアルファベット順にソートされ、セミコロン (;) で区切られます。
const signedHeaders = sortedKeys.join(";")
// リクエストヘッダーを構築します。複数の正規ヘッダーは、ヘッダー名 (小文字) の昇順でソートされた後、連結されます。
const canonicalHeaders = sortedKeys.map(key => `${key}:${signRequest.headers[key]}`).join('\n') + '\n';
const canonicalRequest = [
signRequest.httpMethod,
signRequest.canonicalUri,
canonicalQueryString,
canonicalHeaders,
signedHeaders,
hashedRequestPayload
].join('\n');
console.log('canonicalRequest=========>\n', canonicalRequest);
// ステップ 2: 署名文字列を作成する
const hashedCanonicalRequest = sha256Hex(encoder.encode(canonicalRequest));
const stringToSign = `${ALGORITHM}\n${hashedCanonicalRequest}`;
console.log('stringToSign=========>', stringToSign);
// ステップ 3: 署名を計算する
const signature = hmac256(accessKeySecret, stringToSign);
console.log('signature=========>', signature);
// ステップ 4: Authorization ヘッダーを構築する
const authorization = `${ALGORITHM} Credential=${accessKeyId},SignedHeaders=${signedHeaders},Signature=${signature}`;
console.log('authorization=========>', authorization);
signRequest.headers['Authorization'] = authorization;
} catch (error) {
console.error('Failed to get authorization');
console.error(error);
}
}
async function callApi(signRequest) {
try {
let url = `https://${signRequest.host}${signRequest.canonicalUri}`;
// リクエストパラメーターを追加する
if (signRequest.queryParam) {
const query = new URLSearchParams(signRequest.queryParam);
url += '?' + query.toString();
}
console.log('url=========>', url);
// リクエストオプションを設定する
let options = {
method: signRequest.httpMethod.toUpperCase(),
headers: signRequest.headers
};
// リクエストボディを処理する
if (signRequest.body && ['POST', 'PUT'].includes(signRequest.httpMethod.toUpperCase())) {
options.body = signRequest.body;
}
return (await fetch(url, options)).text();
} catch (error) {
console.error('Failed to send request:', error);
}
}
function percentCode(str) {
return encodeURIComponent(str)
.replace(/\+/g, '%20')
.replace(/\*/g, '%2A')
.replace(/~/g, '%7E');
}
function hmac256(key, data) {
const hmac = crypto.createHmac('sha256', key);
hmac.update(data, 'utf8');
return hmac.digest('hex').toLowerCase();
}
function sha256Hex(bytes) {
const hash = crypto.createHash('sha256');
const digest = hash.update(bytes).digest('hex');
return digest.toLowerCase();
}
function formDataToString(formData) {
const tmp = {};
processObject(tmp, "", formData);
let queryString = '';
for (let [key, value] of Object.entries(tmp)) {
if (queryString !== '') {
queryString += '&';
}
queryString += encodeURIComponent(key) + '=' + encodeURIComponent(value);
}
return queryString;
}
function processObject(map, key, value) {
// 値が null の場合、それ以上の処理は不要です
if (value === null) {
return;
}
if (key === null) {
key = "";
}
// 値が配列の場合、各要素を反復処理し、再帰的に処理します
if (Array.isArray(value)) {
value.forEach((item, index) => {
processObject(map, `${key}.${index + 1}`, item);
});
} else if (typeof value === 'object' && value !== null) {
// 値がオブジェクトの場合、各キーと値のペアを反復処理し、再帰的に処理します
Object.entries(value).forEach(([subKey, subValue]) => {
processObject(map, `${key}.${subKey}`, subValue);
});
} else {
// キーが "." で始まる場合、キーの連続性を維持するために先頭の "." を削除します
if (key.startsWith('.')) {
key = key.slice(1);
}
map[key] = String(value);
}
}
/**
* 署名の例。必要に応じて main メソッドの例のパラメーターを置き換えてください。
* canonicalUri 値を取得するロジックは、ROA API と RPC API の唯一の違いです。残りは同様です。
*
* API メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得し、パラメーターを SignatureRequest にカプセル化します。
* 1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、queryParam を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
* 2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/octet-stream または application/json です。RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
* 3. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/x-www-form-urlencoded です。
*/
// RPC API リクエストの例 1: リクエストパラメーターは "query" にあります
const httpMethod = 'POST'; // リクエストメソッド。ほとんどの RPC API は POST と GET の両方をサポートしています。この例では POST を使用します。
const canonicalUri = '/'; // RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
const host = 'ecs.cn-hangzhou.aliyuncs.com'; // エンドポイント
const xAcsAction = 'DescribeInstanceStatus'; // API 名
const xAcsVersion = '2014-05-26'; // API バージョン番号
const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// DescribeInstanceStatus リクエストパラメーター:
signRequest.queryParam = {
// RegionId はメタデータで String 型、"in":"query"、必須
RegionId: 'cn-hangzhou',
// InstanceId はメタデータで array 型、"in":"query"、必須ではない
InstanceId: ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"],
}
// // RPC API リクエストの例 2: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)
// const httpMethod = 'POST';
// const canonicalUri = '/';
// const host = 'ocr-api.cn-hangzhou.aliyuncs.com';
// const xAcsAction = 'RecognizeGeneral';
// const xAcsVersion = '2021-07-07';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// const filePath = 'D:\\test.png';
// const bytes = fs.readFileSync(filePath);
// // リクエストパラメーターはメタデータで "in": "body" と表示され、パラメーターがボディにあることを意味します。
// signRequest.body = bytes;
// signRequest.headers['content-type'] = 'application/octet-stream';
// // RPC API リクエストの例 3: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)
// const httpMethod = 'POST'; // リクエストメソッド。ほとんどの RPC API は POST と GET の両方をサポートしています。この例では POST を使用します。
// const canonicalUri = '/'; // RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
// const host = 'mt.aliyuncs.com'; // エンドポイント
// const xAcsAction = 'TranslateGeneral'; // API 名
// const xAcsVersion = '2018-10-12'; // API バージョン番号
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// // TranslateGeneral リクエストパラメーター:
// // Context はメタデータで String 型、"in":"query"、必須ではない
// signRequest.queryParam["Context"] = "Morning";
// // FormatType、SourceLanguage、TargetLanguage などのパラメーターはメタデータで "in":"formData" と表示されます
// const formData = {
// SourceLanguage: "zh",
// TargetLanguage: "en",
// FormatType: "text",
// Scene: "general",
// SourceText: 'Hello'
// }
// const str = formDataToString(formData)
// signRequest.body = encoder.encode(str);
// signRequest.headers['content-type'] = 'application/x-www-form-urlencoded';
// // ROA API POST リクエスト
// const httpMethod = 'POST';
// const canonicalUri = '/clusters';
// const host = 'cs.cn-beijing.aliyuncs.com';
// const xAcsAction = 'CreateCluster';
// const xAcsVersion = '2015-12-15';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// // リクエストパラメーターはメタデータで "in": "body" と表示され、パラメーターがボディにあることを意味します。
// const body = {
// name: 'testDemo',
// region_id: 'cn-beijing',
// cluster_type: 'ExternalKubernetes',
// vpcid: 'vpc-2zeou1uod4ylaf35teei9',
// container_cidr: '10.0.0.0/8',
// service_cidr: '172.16.3.0/20',
// security_group_id: 'sg-2ze1a0rlgeo7dj37dd1q',
// vswitch_ids: [
// 'vsw-2zei30dhfldu8ytmtarro'
// ],
// }
// signRequest.body = encoder.encode(JSON.stringify(body));
// signRequest.headers['content-type'] = 'application/json';
// // ROA API GET リクエスト
// const httpMethod = 'GET';
// // canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: percentCode({path_parameter})
// const canonicalUri = '/clusters/' + percentCode("c28c2615f8bfd466b9ef9a76c61706e96") + '/resources';
// const host = 'cs.cn-beijing.aliyuncs.com';
// const xAcsAction = 'DescribeClusterResources';
// const xAcsVersion = '2015-12-15';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// signRequest.queryParam = {
// with_addon_resources: true,
// }
// // ROA API DELETE リクエスト
// const httpMethod = 'DELETE';
// // canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: percentCode({path_parameter})
// const canonicalUri = '/clusters/' + percentCode("c28c2615f8bfd466b9ef9a76c61706e96");
// const host = 'cs.cn-beijing.aliyuncs.com';
// const xAcsAction = 'DeleteCluster';
// const xAcsVersion = '2015-12-15';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
getAuthorization(signRequest);
// API を呼び出す
callApi(signRequest).then(r => {
console.log(r);
}).catch(error => {
console.error(error);
});
PHP の例
サンプルコードは PHP 7.4.33 で実行されます。特定の状況に応じてコードを調整する必要がある場合があります。
<?php
class SignatureDemo
{
// 暗号化アルゴリズム
private $ALGORITHM;
// Access Key ID
private $AccessKeyId;
// Access Key Secret
private $AccessKeySecret;
private $SecurityToken;
public function __construct()
{
date_default_timezone_set('UTC'); // タイムゾーンを GMT に設定
$this->AccessKeyId = getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'); // getenv() は環境変数から RAM ユーザーの Access Key ID を取得します
$this->AccessKeySecret = getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'); // getenv() は環境変数から RAM ユーザーの Access Key Secret を取得します
$this->SecurityToken = getenv('ALIBABA_CLOUD_SECURITY_TOKEN');
$this->ALGORITHM = 'ACS3-HMAC-SHA256'; // 暗号化アルゴリズムを設定
}
/**
* 署名の例。必要に応じて main メソッドの例のパラメーターを置き換えてください。
* canonicalUri 値を取得するロジックは、ROA API と RPC API の唯一の違いです。残りは同様です。
*
* API メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得し、パラメーターを SignatureRequest にカプセル化します。
* 1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、queryParam を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
* 2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/octet-stream または application/json です。RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
* 3. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/x-www-form-urlencoded です。
*/
public function main()
{
// RPC API リクエストの例 1: リクエストパラメーターは "query" にあります
$request = $this->createRequest('POST', '/', 'ecs.cn-hangzhou.aliyuncs.com', 'DescribeInstanceStatus', '2014-05-26');
// DescribeInstanceStatus リクエストパラメーター:
$request['queryParam'] = [
// RegionId はメタデータで String 型、"in":"query"、必須
'RegionId' => 'cn-hangzhou',
// InstanceId はメタデータで array 型、"in":"query"、必須ではない
'InstanceId' => ["i-bp11ht4h2kdXXXXXXXX", "i-bp16maz3h3xgXXXXXXXX", "i-bp10r67hmslXXXXXXXX"]
];
// // RPC API リクエストの例 2: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)
// $request = $this->createRequest('POST', '/', 'ocr-api.cn-hangzhou.aliyuncs.com', 'RecognizeGeneral', '2021-07-07');
// // リクエストパラメーターはメタデータで "in": "body" と表示され、ボディ経由で渡されます。
// $filePath = 'D:\\test.png';
// // バイナリファイルを渡すためにファイルリソースを使用する
// $fileResource = fopen($filePath, 'rb');
// $request['body'] = stream_get_contents($fileResource);
// $request['headers']['content-type'] = 'application/octet-stream'; // Content-Type を application/octet-stream に設定
// // ファイルリソースを閉じる
// fclose($fileResource);
// // RPC API リクエストの例 3: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)
// $request = $this->createRequest('POST', '/', 'mt.aliyuncs.com', 'TranslateGeneral', '2018-10-12');
// // TranslateGeneral リクエストパラメーター:
// $request['queryParam'] = [
// // Context はメタデータで String 型、"in":"query"、必須ではない
// 'Context' => 'Morning',
// ];
// $formData = [
// 'FormatType' => 'text',
// 'SourceLanguage' => 'zh',
// 'TargetLanguage' => 'en',
// 'SourceText' => 'Hello',
// 'Scene' => 'general',
// ];
// $str = self::formDataToString($formData);
// $request['body'] = $str;
// $request['headers']['content-type'] = 'application/x-www-form-urlencoded';
// // ROA API POST リクエスト
// $request = $this->createRequest('POST', '/clusters', 'cs.cn-beijing.aliyuncs.com', 'CreateCluster', '2015-12-15');
// $bodyData = [
// 'name' => 'Test Cluster',
// 'region_id' => 'cn-beijing',
// 'cluster_type' => 'ExternalKubernetes',
// 'vpcid' => 'vpc-2zeou1uod4ylaXXXXXXXX',
// 'service_cidr' => '10.2.0.0/24',
// 'security_group_id' => 'sg-2ze1a0rlgeo7XXXXXXXX',
// "vswitch_ids" => [
// "vsw-2zei30dhfldu8XXXXXXXX"
// ]
// ];
// $request['body'] = json_encode($bodyData, JSON_UNESCAPED_UNICODE);
// $request['headers']['content-type'] = 'application/json; charset=utf-8';
// // ROA API GET リクエスト
// // canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: rawurlencode({path_parameter})
// $cluster_id = 'c930976b3b1fc4e02bc09831dXXXXXXXX';
// $canonicalUri = sprintf("/clusters/%s/resources", rawurlencode($cluster_id));
// $request = $this->createRequest('GET', $canonicalUri, 'cs.cn-beijing.aliyuncs.com', 'DescribeClusterResources', '2015-12-15');
// $request['queryParam'] = [
// 'with_addon_resources' => true,
// ];
// // ROA API DELETE リクエスト
// $cluster_id = 'c930976b3b1fc4e02bc09831dXXXXXXXX';
// $canonicalUri = sprintf("/clusters/%s", rawurlencode($cluster_id));
// $request = $this->createRequest('DELETE', $canonicalUri, 'cs.cn-beijing.aliyuncs.com', 'DeleteCluster', '2015-12-15');
$this->getAuthorization($request);
// API を呼び出す
$this->callApi($request);
}
private function createRequest($httpMethod, $canonicalUri, $host, $xAcsAction, $xAcsVersion)
{
$headers = [
'host' => $host,
'x-acs-action' => $xAcsAction,
'x-acs-version' => $xAcsVersion,
'x-acs-date' => gmdate('Y-m-d\TH:i:s\Z'),
'x-acs-signature-nonce' => bin2hex(random_bytes(16)),
];
return [
'httpMethod' => $httpMethod,
'canonicalUri' => $canonicalUri,
'host' => $host,
'headers' => $headers,
'queryParam' => [],
'body' => null,
];
}
private function getAuthorization(&$request)
{
$request['queryParam'] = $this->processObject($request['queryParam']);
$canonicalQueryString = $this->buildCanonicalQueryString($request['queryParam']);
$hashedRequestPayload = hash('sha256', $request['body'] ?? '');
$request['headers']['x-acs-content-sha256'] = $hashedRequestPayload;
if($this->SecurityToken){
$request['headers']['x-acs-security-token'] = $this->SecurityToken;
}
$canonicalHeaders = $this->buildCanonicalHeaders($request['headers']);
$signedHeaders = $this->buildSignedHeaders($request['headers']);
$canonicalRequest = implode("\n", [
$request['httpMethod'],
$request['canonicalUri'],
$canonicalQueryString,
$canonicalHeaders,
$signedHeaders,
$hashedRequestPayload,
]);
$hashedCanonicalRequest = hash('sha256', $canonicalRequest);
$stringToSign = "{$this->ALGORITHM}\n$hashedCanonicalRequest";
$signature = strtolower(bin2hex(hash_hmac('sha256', $stringToSign, $this->AccessKeySecret, true)));
$authorization = "{$this->ALGORITHM} Credential={$this->AccessKeyId},SignedHeaders=$signedHeaders,Signature=$signature";
$request['headers']['Authorization'] = $authorization;
}
private function callApi($request)
{
try {
// cURL 経由でリクエストを送信する
$url = "https://" . $request['host'] . $request['canonicalUri'];
// URL にリクエストパラメーターを追加する
if (!empty($request['queryParam'])) {
$url .= '?' . http_build_query($request['queryParam']);
}
echo $url;
// cURL セッションを初期化する
$ch = curl_init();
// cURL オプションを設定する
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // SSL 証明書の検証を無効にします。これによりセキュリティが低下するため、本番環境では使用しないでください。(非推奨!!!)
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // コンテンツを返す代わりに、出力しない
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->convertHeadersToArray($request['headers'])); // リクエストヘッダーを追加する
// リクエストタイプに基づいて cURL オプションを設定する
switch ($request['httpMethod']) {
case "GET":
break;
case "POST":
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request['body']);
break;
case "DELETE":
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
break;
default:
echo "Unsupported HTTP method: " . $request['body'];
throw new Exception("Unsupported HTTP method");
}
// リクエストを送信する
$result = curl_exec($ch);
// エラーをチェックする
if (curl_errno($ch)) {
echo "Failed to send request: " . curl_error($ch);
} else {
echo $result;
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
} finally {
// cURL セッションを閉じる
curl_close($ch);
}
}
function formDataToString($formData)
{
$res = self::processObject($formData);
return http_build_query($res);
}
function processObject($value)
{
// 値が null の場合、それ以上の処理は不要です
if ($value === null) {
return;
}
$tmp = [];
foreach ($value as $k => $v) {
if (0 !== strpos($k, '_')) {
$tmp[$k] = $v;
}
}
return self::flatten($tmp);
}
private static function flatten($items = [], $delimiter = '.', $prepend = '')
{
$flatten = [];
foreach ($items as $key => $value) {
$pos = \is_int($key) ? $key + 1 : $key;
if (\is_object($value)) {
$value = get_object_vars($value);
}
if (\is_array($value) && !empty($value)) {
$flatten = array_merge(
$flatten,
self::flatten($value, $delimiter, $prepend . $pos . $delimiter)
);
} else {
if (\is_bool($value)) {
$value = true === $value ? 'true' : 'false';
}
$flatten["$prepend$pos"] = $value;
}
}
return $flatten;
}
private function convertHeadersToArray($headers)
{
$headerArray = [];
foreach ($headers as $key => $value) {
$headerArray[] = "$key: $value";
}
return $headerArray;
}
private function buildCanonicalQueryString($queryParams)
{
ksort($queryParams);
// クエリパラメーターを構築してエンコードする
$params = [];
foreach ($queryParams as $k => $v) {
if (null === $v) {
continue;
}
$str = rawurlencode($k);
if ('' !== $v && null !== $v) {
$str .= '=' . rawurlencode($v);
} else {
$str .= '=';
}
$params[] = $str;
}
return implode('&', $params);
}
private function buildCanonicalHeaders($headers)
{
// ヘッダーをキーでソートし、連結する
uksort($headers, 'strcasecmp');
$canonicalHeaders = '';
foreach ($headers as $key => $value) {
$canonicalHeaders .= strtolower($key) . ':' . trim($value) . "\n";
}
return $canonicalHeaders;
}
private function buildSignedHeaders($headers)
{
// 署名付きヘッダー文字列を構築する
$signedHeaders = array_keys($headers);
sort($signedHeaders, SORT_STRING | SORT_FLAG_CASE);
return implode(';', array_map('strtolower', $signedHeaders));
}
}
$demo = new SignatureDemo();
$demo->main();.NET の例
サンプルコードは .NET 8.0.302 で実行されます。特定の状況に応じてコードを調整する必要がある場合があります。
using System.Globalization;
using System.Net;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using Newtonsoft.Json;
namespace SignatureDemo
{
public class Request
{
public string HttpMethod { get; private set; }
public string CanonicalUri { get; private set; }
public string Host { get; private set; }
public string XAcsAction { get; private set; }
public string XAcsVersion { get; private set; }
public SortedDictionary<string, object> Headers { get; private set; }
public byte[]? Body { get; set; }
public Dictionary<string, object> QueryParam { get; set; }
public Request(string httpMethod, string canonicalUri, string host, string xAcsAction, string xAcsVersion)
{
HttpMethod = httpMethod;
CanonicalUri = canonicalUri;
Host = host;
XAcsAction = xAcsAction;
XAcsVersion = xAcsVersion;
Headers = [];
QueryParam = [];
Body = null;
InitHeader();
}
private void InitHeader()
{
Headers["host"] = Host;
Headers["x-acs-action"] = XAcsAction;
Headers["x-acs-version"] = XAcsVersion;
DateTime utcNow = DateTime.UtcNow;
Headers["x-acs-date"] = utcNow.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture);
Headers["x-acs-signature-nonce"] = Guid.NewGuid().ToString();
}
}
public class Program
{
private static readonly string AccessKeyId = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID") ?? throw new InvalidOperationException("The ALIBABA_CLOUD_ACCESS_KEY_ID environment variable is not set");
private static readonly string AccessKeySecret = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET") ?? throw new InvalidOperationException("The ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable is not set");
private static readonly string? SecurityToken = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_SECURITY_TOKEN");
private const string Algorithm = "ACS3-HMAC-SHA256";
private const string ContentType = "content-type";
/**
* 署名の例。必要に応じて main メソッドの例のパラメーターを置き換えてください。
* canonicalUri 値を取得するロジックは、ROA API と RPC API の唯一の違いです。残りは同様です。
*
* API メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得し、パラメーターを SignatureRequest にカプセル化します。
* 1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、queryParam を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
* 2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/octet-stream または application/json です。RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
* 3. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/x-www-form-urlencoded です。
*/
public static void Main(string[] args)
{
// RPC API リクエストの例 1: リクエストパラメーターは "query" にあります
string httpMethod = "POST"; // リクエストメソッド。ほとんどの RPC API は POST と GET の両方をサポートしています。この例では POST を使用します。
string canonicalUri = "/"; // RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
string host = "ecs.cn-hangzhou.aliyuncs.com"; // クラウドプロダクトのエンドポイント
string xAcsAction = "DescribeInstanceStatus"; // API 名
string xAcsVersion = "2014-05-26"; // API バージョン番号
var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
// DescribeInstanceStatus リクエストパラメーター:
// RegionId はメタデータで String 型、"in":"query"、必須
request.QueryParam["RegionId"] = "cn-hangzhou";
// InstanceId はメタデータで array 型、"in":"query"、必須ではない
List<string> instanceIds = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"];
request.QueryParam["InstanceId"] = instanceIds;
// // RPC API リクエストの例 2: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)
// string httpMethod = "POST";
// string canonicalUri = "/";
// string host = "ocr-api.cn-hangzhou.aliyuncs.com";
// string xAcsAction = "RecognizeGeneral";
// string xAcsVersion = "2021-07-07";
// var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
// // リクエストパラメーターはメタデータで "in": "body" と表示され、ボディ経由で渡されます。
// request.Body = File.ReadAllBytes(@"D:\test.png");
// request.Headers["content-type"] = "application/octet-stream";
// // RPC API リクエストの例 3: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)
// string httpMethod = "POST";
// string canonicalUri = "/";
// string host = "mt.aliyuncs.com";
// string xAcsAction = "TranslateGeneral";
// string xAcsVersion = "2018-10-12";
// var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
// // TranslateGeneral リクエストパラメーター:
// // Context はメタデータで String 型、"in":"query"、必須ではない
// request.QueryParam["Context"] = "Morning";
// // FormatType、SourceLanguage、TargetLanguage などのパラメーターはメタデータで "in":"formData" と表示されます
// var body = new Dictionary<string, object>
// {
// { "FormatType", "text" },
// { "SourceLanguage", "zh" },
// { "TargetLanguage", "en" },
// { "SourceText", "Hello" },
// { "Scene", "general" },
// };
// var str = FormDataToString(body);
// request.Body = Encoding.UTF8.GetBytes(str);
// request.Headers[ContentType] = "application/x-www-form-urlencoded";
// // ROA API POST リクエスト
// String httpMethod = "POST";
// String canonicalUri = "/clusters";
// String host = "cs.cn-beijing.aliyuncs.com";
// String xAcsAction = "CreateCluster";
// String xAcsVersion = "2015-12-15";
// Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
// // リクエストボディ。JsonConvert を使用してボディを JSON 文字列に変換します。
// var body = new SortedDictionary<string, object>
// {
// { "name", "testDemo" },
// { "region_id", "cn-beijing" },
// { "cluster_type", "ExternalKubernetes" },
// { "vpcid", "vpc-2zeou1uod4ylaXXXXXXXX" },
// { "container_cidr", "10.0.0.0/8" },
// { "service_cidr", "172.16.1.0/20" },
// { "security_group_id", "sg-2ze1a0rlgeo7XXXXXXXX" },
// { "vswitch_ids", new List<string>{"vsw-2zei30dhfldu8XXXXXXXX"} },
// };
// string jsonBody = JsonConvert.SerializeObject(body, Formatting.None);
// request.Body = Encoding.UTF8.GetBytes(jsonBody);
// request.Headers[ContentType] = "application/json; charset=utf-8";
// // ROA API GET リクエスト
// String httpMethod = "GET";
// // canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: PercentCode({path_parameter})
// String canonicalUri = "/clusters/" + PercentCode("c81d501a467594eab873edbf2XXXXXXXX") + "/resources";
// String host = "cs.cn-beijing.aliyuncs.com";
// String xAcsAction = "DescribeClusterResources";
// String xAcsVersion = "2015-12-15";
// Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
// request.QueryParam["with_addon_resources"]=true;
// // ROA API DELETE リクエスト
// String httpMethod = "DELETE";
// // canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: PercentCode({path_parameter})
// String canonicalUri = "/clusters/" + PercentCode("c81d501a467594eab873edbf2XXXXXXXX");
// String host = "cs.cn-beijing.aliyuncs.com";
// String xAcsAction = "DeleteCluster";
// String xAcsVersion = "2015-12-15";
// Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
GetAuthorization(request);
// API を呼び出す
var result = CallApiAsync(request);
Console.WriteLine($"result:{result.Result}");
}
private static async Task<string?> CallApiAsync(Request request)
{
try
{
// httpClient を宣言する
using var httpClient = new HttpClient();
// URL を構築する
string url = $"https://{request.Host}{request.CanonicalUri}";
var uriBuilder = new UriBuilder(url);
var query = new List<string>();
// リクエストパラメーターを追加する
foreach (var entry in request.QueryParam.OrderBy(e => e.Key.ToLower()))
{
string value = entry.Value?.ToString() ?? "";
query.Add($"{entry.Key}={Uri.EscapeDataString(value)}");
}
uriBuilder.Query = string.Join("&", query);
Console.WriteLine(uriBuilder.Uri);
var requestMessage = new HttpRequestMessage
{
Method = new HttpMethod(request.HttpMethod),
RequestUri = uriBuilder.Uri,
};
// リクエストヘッダーを設定する
foreach (var entry in request.Headers)
{
if (entry.Key == "Authorization")
{
requestMessage.Headers.TryAddWithoutValidation("Authorization", entry.Value.ToString()); ;
}
else if (entry.Key == ContentType) // main の定義と一致している必要があります
{
continue;
}
else
{
requestMessage.Headers.Add(entry.Key, entry.Value.ToString());
}
}
if (request.Body != null)
{
HttpContent content = new ByteArrayContent(request.Body);
string contentType = request.Headers["content-type"].ToString();
content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
requestMessage.Content = content;
}
// リクエストを送信する
HttpResponseMessage response = await httpClient.SendAsync(requestMessage);
// 応答コンテンツを読み取る
string result = await response.Content.ReadAsStringAsync();
return result;
}
catch (UriFormatException e)
{
Console.WriteLine("Invalid URI syntax");
Console.WriteLine(e.Message);
return null;
}
catch (Exception e)
{
Console.WriteLine("Failed to send request");
Console.WriteLine(e);
return null;
}
}
private static void GetAuthorization(Request request)
{
try
{
// queryParam の List および Map 型のパラメーターを処理し、パラメーターをフラット化する
request.QueryParam = FlattenDictionary(request.QueryParam);
// ステップ 1: 正規リクエスト文字列を作成する
StringBuilder canonicalQueryString = new();
foreach (var entry in request.QueryParam.OrderBy(e => e.Key.ToLower()))
{
if (canonicalQueryString.Length > 0)
{
canonicalQueryString.Append('&');
}
canonicalQueryString.Append($"{PercentCode(entry.Key)}={PercentCode(entry.Value?.ToString() ?? "")}");
}
byte[] requestPayload = request.Body ?? Encoding.UTF8.GetBytes("");
string hashedRequestPayload = Sha256Hash(requestPayload);
request.Headers["x-acs-content-sha256"] = hashedRequestPayload;
if (!string.IsNullOrEmpty(SecurityToken))
{
request.Headers["x-acs-security-token"] = SecurityToken;
}
StringBuilder canonicalHeaders = new();
StringBuilder signedHeadersSb = new();
foreach (var entry in request.Headers.OrderBy(e => e.Key.ToLower()))
{
if (entry.Key.StartsWith("x-acs-", StringComparison.CurrentCultureIgnoreCase) || entry.Key.Equals("host", StringComparison.OrdinalIgnoreCase) || entry.Key.Equals(ContentType, StringComparison.OrdinalIgnoreCase))
{
string lowerKey = entry.Key.ToLower();
string value = (entry.Value?.ToString() ?? "").Trim();
canonicalHeaders.Append($"{lowerKey}:{value}\n");
signedHeadersSb.Append($"{lowerKey};");
}
}
string signedHeaders = signedHeadersSb.ToString().TrimEnd(';');
string canonicalRequest = $"{request.HttpMethod}\n{request.CanonicalUri}\n{canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\n{hashedRequestPayload}";
Console.WriteLine($"canonicalRequest:{canonicalRequest}");
// ステップ 2: 署名文字列を作成する
string hashedCanonicalRequest = Sha256Hash(Encoding.UTF8.GetBytes(canonicalRequest));
string stringToSign = $"{Algorithm}\n{hashedCanonicalRequest}";
Console.WriteLine($"stringToSign:{stringToSign}");
// ステップ 3: 署名を計算する
string signature = HmacSha256(AccessKeySecret, stringToSign);
// ステップ 4: Authorization ヘッダーを構築する
string authorization = $"{Algorithm} Credential={AccessKeyId},SignedHeaders={signedHeaders},Signature={signature}";
request.Headers["Authorization"] = authorization;
Console.WriteLine($"authorization:{authorization}");
}
catch (Exception ex)
{
Console.WriteLine("Failed to get authorization");
Console.WriteLine(ex.Message);
}
}
private static string FormDataToString(Dictionary<string, object> formData)
{
Dictionary<string, object> tileMap = FlattenDictionary( formData);
StringBuilder result = new StringBuilder();
bool first = true;
string symbol = "&";
foreach (var entry in tileMap)
{
string value = entry.Value?.ToString() ?? "";
if (!string.IsNullOrEmpty(value))
{
if (!first)
{
result.Append(symbol);
}
first = false;
result.Append(PercentCode(entry.Key));
result.Append("=");
result.Append(PercentCode(value));
}
}
return result.ToString();
}
private static Dictionary<string, object> FlattenDictionary(Dictionary<string, object> dictionary, string prefix = "")
{
var result = new Dictionary<string, object>();
foreach (var kvp in dictionary)
{
string key = string.IsNullOrEmpty(prefix) ? kvp.Key : $"{prefix}.{kvp.Key}";
if (kvp.Value is Dictionary<string, object> nestedDict)
{
var nestedResult = FlattenDictionary(nestedDict, key);
foreach (var nestedKvp in nestedResult)
{
result[nestedKvp.Key] = nestedKvp.Value;
}
}
else if (kvp.Value is List<string> list)
{
for (int i = 0; i < list.Count; i++)
{
result[$"{key}.{i + 1}"] = list[i];
}
}
else
{
result[key] = kvp.Value;
}
}
return result;
}
private static string HmacSha256(string key, string message)
{
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
{
byte[] hashMessage = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
return BitConverter.ToString(hashMessage).Replace("-", "").ToLower();
}
}
private static string Sha256Hash(byte[] input)
{
byte[] hashBytes = SHA256.HashData(input);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
private static string PercentCode(string str)
{
if (string.IsNullOrEmpty(str))
{
throw new ArgumentException("Input string cannot be null or empty");
}
return Uri.EscapeDataString(str).Replace("+", "%20").Replace("*", "%2A").Replace("%7E", "~");
}
}
}Rust の例
サンプルコードは rustc 1.82.0 で実行されます。特定の状況に応じてコードを調整する必要がある場合があります。
Rust の例を実行するには、Cargo.toml ファイルに次の依存関係を追加します。
[dependencies]
serde = { version = "1.0" }
serde_json = "1.0"
rand = "0.8"
base64 = "0.21"
sha2 = "0.10"
chrono = "0.4"
hmac = "0.12"
hex = "0.4"
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
percent-encoding = "2.1"use core::str;
use std::collections::{BTreeMap, HashMap};
use std::env;
use std::time::{SystemTime, SystemTimeError};
use chrono::DateTime;
use hmac::{Hmac, Mac};
use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
use rand::Rng;
use serde_json::{json, Value};
use std::borrow::Cow;
use reqwest::{
Client,
header::{HeaderMap, HeaderValue}, Method, Response, StatusCode,
};
use sha2::{Digest, Sha256};
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
// x-acs-date を生成
pub fn current_timestamp() -> Result<u64, SystemTimeError> {
Ok(SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs())
}
// URL エンコーディング
pub fn percent_code(encode_str: &str) -> Cow<'_, str> {
let encoded = utf8_percent_encode(encode_str, NON_ALPHANUMERIC)
.to_string()
.replace("+", "20%")
.replace("%5F", "_")
.replace("%2D", "-")
.replace("%2E", ".")
.replace("%7E", "~");
Cow::Owned(encoded) // String または &str を保持できる Cow<str> を返す
}
fn flatten_target_ops(
targets: Vec<HashMap<&str, &str>>,
base_key: &str,
) -> Vec<(&'static str, &'static str)> {
let mut result = Vec::new();
for (idx, item) in targets.iter().enumerate() {
let prefix = format!("{}.{}", base_key, idx + 1);
for (&k, &v) in item {
let key = format!("{}.{}", prefix, k);
let key_static: &'static str = Box::leak(key.into_boxed_str());
let value_static: &'static str = Box::leak(v.to_string().into_boxed_str());
result.push((key_static, value_static));
}
}
result
}
/// SHA-256 ハッシュを計算
pub fn sha256_hex(message: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(message);
format!("{:x}", hasher.finalize()).to_lowercase()
}
// HMAC-SHA256
pub fn hmac256(key: &[u8], message: &str) -> Result<Vec<u8>, String> {
let mut mac = Hmac::<Sha256>::new_from_slice(key)
.map_err(|e| format!("use data key on sha256 fail:{}", e))?;
mac.update(message.as_bytes());
let signature = mac.finalize();
Ok(signature.into_bytes().to_vec())
}
// 署名用の一意の乱数を生成
pub fn generate_random_string(length: usize) -> String {
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
let mut rng = rand::thread_rng();
(0..length)
.map(|_| CHARSET[rng.gen_range(0..CHARSET.len())] as char)
.collect()
}
pub fn generate_nonce() -> String {
generate_random_string(32)
}
// 正規クエリ文字列を構築 (エンコード済み)
pub fn build_sored_encoded_query_string(query_params: &[(&str, &str)]) -> String {
let sorted_query_params: BTreeMap<_, _> = query_params.iter().copied().collect();
let encoded_params: Vec<String> = sorted_query_params
.into_iter()
.map(|(k, v)| {
let encoded_key = percent_code(k);
let encoded_value = percent_code(v);
format!("{}={}", encoded_key, encoded_value)
})
.collect();
encoded_params.join("&")
}
// 応答を読み取る
pub async fn read_response(result: Response) -> Result<(StatusCode, String), String> {
let status = result.status();
let data = result.bytes().await.map_err(|e| format!("Read response body failed: {}", e))?;
let res = match str::from_utf8(&data) {
Ok(s) => s.to_string(),
Err(_) => return Err("Body contains non UTF-8 characters".to_string()),
};
Ok((status, res))
}
// FormData の値の型を定義
#[derive(Debug, Clone)]
pub enum FormValue {
String(String),
Vec(Vec<String>),
HashMap(HashMap<String, String>),
}
// Json、Binary、FormData を含むリクエストボディの型を統一的に扱うためのリクエストボディ列挙型を定義
pub enum RequestBody {
Json(HashMap<String, Value>), // Json
Binary(Vec<u8>), // バイナリ
FormData(HashMap<String, FormValue>), // FormData
None,
}
// 正規リクエスト
pub async fn call_api(
client: Client,
method: Method,
host: &str,
canonical_uri: &str,
query_params: &[(&str, &str)],
action: &str,
version: &str,
body: RequestBody,
access_key_id: &str,
access_key_secret: &str,
) -> Result<String, String> {
// ボディの型に基づいてリクエストボディのコンテンツを処理し、処理されたコンテンツを body_content 変数に保存する
let body_content = match &body {
RequestBody::Json(body_map) => json!(body_map).to_string(),
RequestBody::Binary(binary_data) => {
STANDARD.encode(binary_data)
},
RequestBody::FormData(form_data) => {
let params: Vec<String> = form_data
.iter()
.flat_map(|(k, v)| {
match v {
FormValue::String(s) => {
vec![format!("{}={}", percent_code(k), percent_code(&s))]
},
FormValue::Vec(vec) => {
vec.iter()
.map(|s| format!("{}={}", percent_code(k), percent_code(s)))
.collect::<Vec<_>>()
},
FormValue::HashMap(map) => {
map.iter()
.map(|(sk, sv)| format!("{}={}", percent_code(sk), percent_code(sv)))
.collect::<Vec<_>>()
},
}
})
.collect();
params.join("&")
},
RequestBody::None => String::new(),
};
// リクエストボディの x-acs-content-sha256 を計算し、x-acs-date、x-acs-signature-nonce、および署名するリクエストヘッダーを準備する
let hashed_request_payload = if body_content.is_empty() {
sha256_hex("")
} else {
sha256_hex(&body_content)
};
// x-acs-date
let now_time = current_timestamp().map_err(|e| format!("Get current timestamp failed: {}", e))?;
let datetime = DateTime::from_timestamp(now_time as i64, 0).ok_or_else(|| format!("Get datetime from timestamp failed: {}", now_time))?;
let datetime_str = datetime.format("%Y-%m-%dT%H:%M:%SZ").to_string();
// x-acs-signature-nonce
let signature_nonce = generate_nonce();
println!("Signature Nonce: {}", signature_nonce);
// 署名するリクエストヘッダー
let sign_header_arr = &[
"host",
"x-acs-action",
"x-acs-content-sha256",
"x-acs-date",
"x-acs-signature-nonce",
"x-acs-version",
];
let sign_headers = sign_header_arr.join(";");
// 1. 正規リクエストヘッダーを構築する
let mut headers = HeaderMap::new();
headers.insert("Host", HeaderValue::from_str(host).unwrap());
headers.insert("x-acs-action", HeaderValue::from_str(action).unwrap());
headers.insert("x-acs-version", HeaderValue::from_str(version).unwrap());
headers.insert("x-acs-date", HeaderValue::from_str(&datetime_str).unwrap());
headers.insert("x-acs-signature-nonce", HeaderValue::from_str(&signature_nonce).unwrap());
headers.insert("x-acs-content-sha256", HeaderValue::from_str(&hashed_request_payload).unwrap());
// 2. 署名するリクエストヘッダーを構築する
let canonical_query_string = build_sored_encoded_query_string(query_params); // パラメーターをエンコードして連結する
println!("CanonicalQueryString: {}", canonical_query_string);
let canonical_request = format!(
"{}\n{}\n{}\n{}\n\n{}\n{}",
method.as_str(),
canonical_uri,
canonical_query_string,
sign_header_arr.iter().map(|&header| format!("{}:{}", header, headers[header].to_str().unwrap())).collect::<Vec<_>>().join("\n"),
sign_headers,
hashed_request_payload
);
println!("Canonical Request: {}", canonical_request);
// 3. 署名するリクエストヘッダーの SHA-256 ハッシュを計算する
let result = sha256_hex(&canonical_request);
// 4. 署名文字列を構築する
let string_to_sign = format!("ACS3-HMAC-SHA256\n{}", result);
// 5. 署名を計算する
let signature = hmac256(access_key_secret.as_bytes(), &string_to_sign)?;
let data_sign = hex::encode(&signature);
let auth_data = format!(
"ACS3-HMAC-SHA256 Credential={},SignedHeaders={},Signature={}",
access_key_id, sign_headers, data_sign
);
// 6. Authorization ヘッダーを構築する
headers.insert("Authorization", HeaderValue::from_str(&auth_data).unwrap());
// URL を構築し、リクエストパラメーターを連結する
let url: String;
if !query_params.is_empty() {
url = format!("https://{}{}?{}", host, canonical_uri,canonical_query_string);
} else {
url = format!("https://{}{}", host, canonical_uri);
}
// リクエストを送信する呼び出し
let response = send_request(
&client,
method,
&url,
headers,
query_params,
&body,
&body_content,
)
.await?;
// 応答を読み取る
let (_, res) = read_response(response).await?;
Ok(res)
}
/// リクエストを送信する
async fn send_request(
client: &Client,
method: Method,
url: &str,
headers: HeaderMap,
query_params: &[(&str, &str)], // クエリパラメーターを受け取る
body: &RequestBody, // ボディのデータ型を決定するために使用
body_content: &str, // ボディが空でない場合にボディリクエストパラメーター (FormData/Json/Binary) を受け取る
) -> Result<Response, String> {
let mut request_builder = client.request(method.clone(), url);
// リクエストヘッダーを追加する
for (k, v) in headers.iter() {
request_builder = request_builder.header(k, v.clone());
}
// リクエストボディを追加する
match body {
RequestBody::Binary(_) => {
request_builder = request_builder.header("Content-Type", "application/octet-stream");
request_builder = request_builder.body(body_content.to_string()); // ここに値を移動
}
RequestBody::Json(_) => {
// ボディがマップで空でない場合、JSON に変換し、body_content 変数に保存し、application/json; charset=utf-8 を設定する
if !body_content.is_empty() {
request_builder = request_builder.body(body_content.to_string());
request_builder = request_builder.header("Content-Type", "application/json; charset=utf-8");
}
}
RequestBody::FormData(_) => {
// form-data 型を処理し、content-type を設定する
if !body_content.is_empty() {
request_builder = request_builder.header("Content-Type", "application/x-www-form-urlencoded");
request_builder = request_builder.body(body_content.to_string());
}
}
RequestBody::None => {
request_builder = request_builder.body(String::new());
}
}
// リクエストを構築する
let request = request_builder
.build()
.map_err(|e| format!("build request fail: {}", e))?;
// リクエストを送信する
let response = client
.execute(request)
.await
.map_err(|e| format!("execute request fail: {}", e))?;
// 結果を返す
Ok(response)
}
/**
*
* 署名の例。必要に応じて main メソッドの例のパラメーターを置き換えてください。
* <p>
* API メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得します。
* 1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、query_params を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
* 2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/octet-stream または application/json です。RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
* 2. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/x-www-form-urlencoded です。
*/
#[tokio::main]
async fn main() {
// HTTP クライアントを作成する
let client = Client::new();
// env::var() は環境変数から Access Key ID と Access Key Secret を取得します
let access_key_id = env::var("ALIBABA_CLOUD_ACCESS_KEY_ID").expect("Cannot get access key id.");
let access_key_secret = env::var("ALIBABA_CLOUD_ACCESS_KEY_SECRET").expect("Cannot get access key id.");
let access_key_id: &str = &access_key_id;
let access_key_secret: &str = &access_key_secret;
// RPC API リクエストの例 1: リクエストパラメーターは "query" にあります POST
let method = Method::POST; // リクエストメソッド
let host = "ecs.cn-hangzhou.aliyuncs.com"; // エンドポイント
let canonical_uri = "/"; // RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
let action = "DescribeInstanceStatus"; // API 名
let version = "2014-05-26"; // API バージョン番号
let region_id = "cn-hangzhou";
let instance_ids = vec![
"i-bp11ht4XXXXXXXX",
"i-bp16mazXXXXXXXX",
];
let mut query: Vec<(&str, &str)> = Vec::new();
query.push(("RegionId", region_id));
for (index, instance_id) in instance_ids.iter().enumerate() {
let key = format!("InstanceId.{}", index + 1);
query.push((Box::leak(key.into_boxed_str()), instance_id));
}
// クエリパラメーター
let query_params: &[(&str, &str)] = &query;
// リクエストボディが空の場合
let body = RequestBody:: None;
// 複雑なクエリパラメーターを持つ RPC API "in":"query" POST
// let method = Method::POST; // リクエストメソッド
// let host = "tds.cn-shanghai.aliyuncs.com"; // エンドポイント
// let canonical_uri = "/"; // RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
// let action = "AddAssetSelectionCriteria"; // API 名
// let version = "2018-12-03"; // API バージョン番号
// パラメーターを定義
// let mut target_op = HashMap::new();
// target_op.insert("Operation", "add");
// target_op.insert("Target", "i-2ze1j7ocdXXXXXXXX");
// パラメーター TargetOperationList を定義し、コレクションにマップ型を渡す
// let target_operation_list = vec![target_op];
// パラメーターをフラット化
// let mut query = flatten_target_ops(target_operation_list, "TargetOperationList");
// 通常のパラメーター
// query.push(("SelectionKey", "85a561b7-27d5-47ad-a0ec-XXXXXXXX"));
// let query_params: &[(&str, &str)] = &query;
// let body = RequestBody:: None;
// RPC API リクエストの例 2: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ) POST
// let method = Method::POST; // リクエストメソッド
// let host = "ocr-api.cn-hangzhou.aliyuncs.com";
// let canonical_uri = "/";
// let action = "RecognizeGeneral";
// let version = "2021-07-07";
// リクエストパラメーター "in":"body" バイナリファイル型
// let binary_data = std::fs::read("<FILE_PATH>").expect("Failed to read file"); // <FILE_PATH> を実際のファイルパスに置き換える
// ボディがバイナリ型の場合
// let body = RequestBody::Binary(binary_data);
// クエリパラメーターは空
// let query_params = &[];
// RPC API リクエストの例 3: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ) POST
// let method = Method::POST; // リクエストメソッド
// let host = "mt.aliyuncs.com";
// let canonical_uri = "/";
// let action = "TranslateGeneral";
// let version = "2018-10-12";
// // FormatType、SourceLanguage、TargetLanguage などのパラメーターはメタデータで "in":"formData" と表示されます
// let mut form_data = HashMap::new(); // body 型は FormData(HashMap<String, FormValue>)。FormValue は Vec<String>、HashSet<String>、または HashMap<String, String> などをサポートします。FormValue enum にさらに型を追加できます。
// form_data.insert(String::from("FormatType"),FormValue::String(String::from("text")));
// form_data.insert(String::from("SourceLanguage"),FormValue::String(String::from("zh")));
// form_data.insert(String::from("TargetLanguage"),FormValue::String(String::from("en")));
// form_data.insert(String::from("SourceText"),FormValue::String(String::from("Hello")));
// form_data.insert(String::from("Scene"),FormValue::String(String::from("general")));
// // クエリパラメーター
// let query_params = &[("Context", "Morning")];
// // ボディが FormData 型の場合、"in":"formdata"
// let body = RequestBody::FormData(form_data);
// ROA API POST リクエスト API: CreateCluster
// API リクエスト定数を定義
// let method = Method::POST; // リクエストメソッド
// let host = "cs.cn-hangzhou.aliyuncs.com";
// let canonical_uri = "/clusters";
// let action = "CreateCluster";
// let version = "2015-12-15";
// // リクエストボディパラメーターを設定
// let mut body_json = HashMap::new(); // body 型は Json(HashMap<String, Value>)。Value は次の型をサポートします: Value::String("test".to_string()) // 文字列, Value::Number(serde_json::Number::from(42)) // 数値, Value::Bool(true) // ブール値, Value::Null // Null, Value::Array(vec![Value::from(1), Value::from(2), Value::from(3)]) //配列, json!({"nested_key": "nested_value"})
// body_json.insert(String::from("name"),json!("Test Cluster"));
// body_json.insert(String::from("region_id"),json!("cn-hangzhou"));
// body_json.insert(String::from("cluster_type"),json!("ExternalKubernetes"));
// body_json.insert(String::from("vpcid"),json!("vpc-2zeou1uodXXXXXXXX"));
// body_json.insert(String::from("container_cidr"),json!("10.X.X.X/X"));
// body_json.insert(String::from("service_cidr"),json!("10.X.X.X/X"));
// body_json.insert(String::from("security_group_id"),json!("sg-2ze1a0rlgXXXXXXXX"));
// body_json.insert(
// String::from("vswitch_ids"),
// Value::Array(vec![
// Value::from("vsw-2zei30dhflXXXXXXXX"),
// Value::from("vsw-2zei30dhflXXXXXXXX"),
// Value::from("vsw-2zei30dhflXXXXXXXX"),
// ]),
// );
// // クエリパラメーターは空
// let query_params = &[];
// // ボディが Json 型の場合
// let body = RequestBody::Json(body_json);
// ROA API GET リクエスト API: DeleteCluster 指定されたクラスターのリンクされたリソースをクエリ
// let method = Method::GET; // リクエストメソッド
// let host = "cs.cn-hangzhou.aliyuncs.com"; // エンドポイント
// // リソースパスを連結
// let uri = format!("/clusters/{}/resources", percent_code("ce196d21571a64be9XXXXXXXX").as_ref());
// let canonical_uri = uri.as_str(); // リソースパス、&str 型に変換
// let action = "DescribeClusterResources"; // API 名
// let version = "2015-12-15"; // API バージョン番号
// // クエリパラメーターを設定
// let query_params = &[("with_addon_resources", if true { "true" } else { "false" })]; // "true" または "false"
// // ボディパラメーターを空に設定
// let body = RequestBody:: None;
// ROA API DELETE リクエスト API: DeleteCluster 従量課金クラスターを削除する DELETE リクエスト
// let method = Method::DELETE;
// let host = "cs.cn-hangzhou.aliyuncs.com";
// let uri = format!("/clusters/{}", percent_code("ce0138ff31ad044f8XXXXXXXX").as_ref());
// let canonical_uri = uri.as_str(); // リソースパス、&str 型に変換
// let action = "DeleteCluster";
// let version = "2015-12-15";
// // クエリパラメーター
// let query_params = &[];
// // ボディパラメーターが空の場合
// let body = RequestBody:: None;
// SendSms API
// let method = Method::POST; // リクエストメソッド
// let host = "dysmsapi.aliyuncs.com"; // エンドポイント
// let canonical_uri = "/"; // RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
// let action = "SendSms"; // API 名
// let version = "2017-05-25"; // API バージョン番号
// let mut query: Vec<(&str, &str)> = Vec::new();
// query.push(("PhoneNumbers", "<YOUR_PHONENUMBERS>"));
// query.push(("TemplateCode", "<YOUR_TEMPLATECODE>"));
// query.push(("SignName", "<YOUR_SIGNNAME>"));
// query.push(("TemplateParam", "<YOUR_TEMPLATEPARAM>"));
// // クエリパラメーター
// let query_params: &[(&str, &str)] = &query;
// // リクエストボディが空の場合
// let body = RequestBody:: None;
// リクエストを送信する
match call_api(
client.clone(),
method, // API リクエストメソッド POST/GET/DELETE
host, // API エンドポイント
canonical_uri, // API リソースパス
query_params, // "in":"query" クエリパラメーター
action, // API 名
version, // API バージョン番号
body, // "in":"body" リクエストボディパラメーター、Json/FormData/Binary 型をサポート
access_key_id,
access_key_secret,
)
.await {
Ok(response) => println!("Response: {}", response),
Err(error) => eprintln!("Exception: {}", error),
}
}
シェルスクリプトの例
#!/bin/bash
accessKey_id="<YOUR-ACCESSKEY-ID>"
accessKey_secret="<YOUR-ACCESSKEY-SECRET>"
algorithm="ACS3-HMAC-SHA256"
# リクエストパラメーター -- 必要に応じてこのセクションを変更してください
httpMethod="POST"
host="dns.aliyuncs.com"
queryParam=("DomainName=example.com" "RRKeyWord=@")
action="DescribeDomainRecords"
version="2015-01-09"
canonicalURI="/"
# body-type または formdata-type のパラメーターをボディ経由で渡す
# body-type パラメーター: body の値は JSON 文字列です: "{'key1':'value1','key2':'value2'}"、署名ヘッダーに content-type:application/json; charset=utf-8 を追加する必要があります。
# body-type パラメーターがバイナリファイルの場合: body を変更する必要はなく、署名ヘッダーに content-type:application/octet-stream を追加し、curl_command に --data-binary パラメーターを追加するだけです。
# formdata-type パラメーター: body パラメーターの形式は "key1=value1&key2=value2" で、署名ヘッダーに content-type:application/x-www-form-urlencoded を追加する必要があります。
body=""
# ISO 8601 形式の UTC 時刻
utc_timestamp=$(date +%s)
utc_date=$(date -u -d @${utc_timestamp} +"%Y-%m-%dT%H:%M:%SZ")
# x-acs-signature-nonce 乱数
random=$(uuidgen | sed 's/-//g')
# 署名ヘッダー
headers="host:${host}
x-acs-action:${action}
x-acs-version:${version}
x-acs-date:${utc_date}
x-acs-signature-nonce:${random}"
# URL エンコーディング関数
urlencode() {
local string="${1}"
local strlen=${#string}
local encoded=""
local pos c o
for (( pos=0 ; pos<strlen ; pos++ )); do
c=${string:$pos:1}
case "$c" in
[-_.~a-zA-Z0-9] ) o="${c}" ;;
* ) printf -v o '%%%02X' "'$c"
esac
encoded+="${o}"
done
echo "${encoded}"
}
# ステップ 1: 正規リクエスト文字列を作成する
# queryParam のすべてのパラメーターをフラット化する
newQueryParam=()
# 各元のパラメーターを走査する
for param in "${queryParam[@]}"; do
# 等号が含まれているかどうかをチェックして、キーと値のペアであるかどうかを判断する
if [[ "$param" == *"="* ]]; then
# キーと値を分割する
IFS='=' read -r key value <<< "$param"
# 値を URL エンコードする
value=$(urlencode "$value")
# 値がリストであるかどうかをチェックする (括弧を探す)
if [[ "$value" =~ ^\(.+\)$ ]]; then
# 括弧を削除する
value="${value:1:-1}"
# IFS を使用して値リストを分割する
IFS=' ' read -ra values <<< "$value"
# 各値にインデックスを追加する
index=1
for val in "${values[@]}"; do
# 二重引用符を削除する
val="${val%\"}"
val="${val#\"}"
# 新しい配列に追加する
newQueryParam+=("$key.$index=$val")
((index++))
done
else
# リストでない場合は、直接追加する
newQueryParam+=("$key=$value")
fi
else
# 等号がない場合は、そのまま保持する
newQueryParam+=("$param")
fi
done
# 新しいクエリパラメーターを処理してソートする
sortedParams=()
declare -A paramsMap
for param in "${newQueryParam[@]}"; do
IFS='=' read -r key value <<< "$param"
paramsMap["$key"]="$value"
done
# キーでソートする
for key in $(echo ${!paramsMap[@]} | tr ' ' '\n' | LC_ALL=C sort); do
sortedParams+=("$key=${paramsMap[$key]}")
done
# 1.1 正規クエリ文字列を構築する
canonicalQueryString=""
first=true
for item in "${sortedParams[@]}"; do
[ "$first" = true ] && first=false || canonicalQueryString+="&"
# 等号が存在するかどうかをチェックする
if [[ "$item" == *=* ]]; then
canonicalQueryString+="$item"
else
canonicalQueryString+="$item="
fi
done
# 1.2 リクエストボディを処理する
hashedRequestPayload=$(echo -n "$body" | openssl dgst -sha256 | awk '{print $2}')
headers="${headers}
x-acs-content-sha256:$hashedRequestPayload"
# 1.3 正規リクエストヘッダーを構築する
canonicalHeaders=$(echo "$headers" | grep -E '^(host|content-type|x-acs-)' | while read line; do
key=$(echo "$line" | cut -d':' -f1 | tr '[:upper:]' '[:lower:]')
value=$(echo "$line" | cut -d':' -f2-)
echo "${key}:${value}"
done | sort | tr '\n' '\n')
signedHeaders=$(echo "$headers" | grep -E '^(host|content-type|x-acs-)' | while read line; do
key=$(echo "$line" | cut -d':' -f1 | tr '[:upper:]' '[:lower:]')
echo "$key"
done | sort | tr '\n' ';' | sed 's/;$//')
# 1.4 正規リクエストを構築する
canonicalRequest="${httpMethod}\n${canonicalURI}\n${canonicalQueryString}\n${canonicalHeaders}\n\n${signedHeaders}\n${hashedRequestPayload}"
echo -e "canonicalRequest=${canonicalRequest}"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++++"
str=$(echo "$canonicalRequest" | sed 's/%/%%/g')
hashedCanonicalRequest=$(printf "${str}" | openssl sha256 -hex | awk '{print $2}')
# ステップ 2: 署名文字列を作成する
stringToSign="${algorithm}\n${hashedCanonicalRequest}"
echo -e "stringToSign=$stringToSign"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++++"
# ステップ 3: 署名を計算する
signature=$(printf "${stringToSign}" | openssl dgst -sha256 -hmac "${accessKey_secret}" | sed 's/^.* //')
echo -e "signature=${signature}"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++++"
# ステップ 4: Authorization ヘッダーを構築する
authorization="${algorithm} Credential=${accessKey_id},SignedHeaders=${signedHeaders},Signature=${signature}"
echo -e "authorization=${authorization}"
# curl コマンドを構築する
url="https://$host$canonicalURI"
curl_command="curl -X $httpMethod '$url?$canonicalQueryString'"
# リクエストヘッダーを追加する
IFS=$'\n' # 改行を新しい IFS として設定
for header in $headers; do
curl_command="$curl_command -H '$header'"
done
curl_command+=" -H 'Authorization:$authorization'"
# body-type パラメーターがバイナリファイルの場合、次の行をコメントアウトする
curl_command+=" -d '$body'"
# body-type パラメーターがバイナリファイルの場合、次の行のコメントを解除する
#curl_command+=" --data-binary @"/root/001.png" "
echo "$curl_command"
# curl コマンドを実行する
eval "$curl_command"C の例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#include <stdint.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <openssl/rand.h>
#include <curl/curl.h>
// getenv() は環境変数から Access Key ID と Access Key Secret を取得します
#define ACCESS_KEY_ID getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
#define ACCESS_KEY_SECRET getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
#define ALGORITHM "ACS3-HMAC-SHA256"
#define BUFFER_SIZE 4096
// ソート用の構造体
typedef struct {
char key[256];
char value[256];
} KeyValuePair;
// 比較関数: キーを辞書順でソート
int compare_pairs(const void *a, const void *b) {
return strcmp(((const KeyValuePair *)a)->key, ((const KeyValuePair *)b)->key);
}
// URL エンコーディング
char* percentEncode(const char* str) {
if (str == NULL) {
fprintf(stderr, "Input string cannot be null\n");
return NULL;
}
size_t len = strlen(str);
char* encoded = (char*)malloc(len * 3 + 1);
if (encoded == NULL) {
fprintf(stderr, "Memory allocation failed\n");
free(encoded);
return NULL;
}
char* ptr = encoded;
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)str[i];
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
*ptr++ = c;
} else {
ptr += sprintf(ptr, "%%%02X", c);
}
}
*ptr = '\0';
char* finalEncoded = malloc(strlen(encoded) + 1);
if (finalEncoded) {
char* fptr = finalEncoded;
for (size_t j = 0; j < strlen(encoded); j++) {
if (encoded[j] == '+') {
strcpy(fptr, "%20");
fptr += 3;
} else if (encoded[j] == '*') {
strcpy(fptr, "%2A");
fptr += 3;
} else if (encoded[j] == '~') {
*fptr++ = '~';
} else {
*fptr++ = encoded[j];
}
}
*fptr = '\0';
}
free(encoded);
return finalEncoded;
}
/**
* @brief クエリパラメーターを URL エンコードし、辞書順にソートして、正規クエリ文字列を生成します。
* @param query_params 元のクエリパラメーター文字列 (例: "key1=value1&key2=value2")。
* @return char* ソートおよびエンコードされた正規クエリ文字列 (呼び出し元はメモリを解放する必要があります)。
*/
char* generate_sorted_encoded_query(const char* query_params) {
if (query_params == NULL || strlen(query_params) == 0) {
return strdup(""); // 空のパラメーターには空の文字列を返す
}
KeyValuePair pairs[100]; // 最大 100 個のキーと値のペアをサポート
int pair_count = 0;
char* copy = strdup(query_params);
if (!copy) {
fprintf(stderr, "Memory allocation failed\n");
return NULL;
}
char* token = NULL;
char* saveptr = NULL;
token = strtok_r(copy, "&", &saveptr);
while (token != NULL && pair_count < 100) {
char* eq = strchr(token, '=');
if (eq) {
size_t key_len = eq - token;
char key[256], value[256];
strncpy(key, token, key_len);
key[key_len] = '\0';
const char* val = eq + 1;
strncpy(value, val, sizeof(value) - 1);
value[sizeof(value) - 1] = '\0';
char* encoded_key = percentEncode(key);
char* encoded_value = percentEncode(value);
strncpy(pairs[pair_count].key, encoded_key, sizeof(pairs[pair_count].key));
strncpy(pairs[pair_count].value, encoded_value, sizeof(pairs[pair_count].value));
pair_count++;
free(encoded_key);
free(encoded_value);
}
token = strtok_r(NULL, "&", &saveptr);
}
free(copy);
// キーでソート
qsort(pairs, pair_count, sizeof(KeyValuePair), compare_pairs);
// ソートされたクエリ文字列を連結
char* query_sorted = malloc(BUFFER_SIZE);
if (!query_sorted) {
fprintf(stderr, "Memory allocation failed\n");
return NULL;
}
query_sorted[0] = '\0';
for (int i = 0; i < pair_count; ++i) {
if (i == 0) {
snprintf(query_sorted, BUFFER_SIZE, "%s=%s", pairs[i].key, pairs[i].value);
} else {
char temp[512];
snprintf(temp, sizeof(temp), "&%s=%s", pairs[i].key, pairs[i].value);
strncat(query_sorted, temp, BUFFER_SIZE - strlen(query_sorted) - 1);
}
}
return query_sorted;
}
// HMAC-SHA256 計算
void hmac256(const char *key, const char *message, char *output) {
unsigned char hmac[SHA256_DIGEST_LENGTH];
unsigned int result_len;
HMAC(EVP_sha256(), key, strlen(key), (unsigned char *)message, strlen(message), hmac, &result_len);
for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
sprintf(output + (i * 2), "%02x", hmac[i]);
}
output[SHA256_DIGEST_LENGTH * 2] = '\0';
}
// SHA-256 ハッシュを計算
void sha256_hex(const char *input, char *output) {
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((unsigned char *)input, strlen(input), hash);
for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
sprintf(output + (i * 2), "%02x", hash[i]);
}
output[SHA256_DIGEST_LENGTH * 2] = '\0';
}
// x-acs-signature-nonce を生成するために使用
void generate_uuid(char *uuid, size_t size) {
if (size < 37) {
fprintf(stderr, "Buffer size too small for UUID\n");
return;
}
unsigned char random_bytes[16];
RAND_bytes(random_bytes, sizeof(random_bytes));
random_bytes[6] &= 0x0f; // 上位 4 ビットを保持
random_bytes[6] |= 0x40; // バージョンを 4 に設定
random_bytes[8] &= 0x3f; // 上位 2 ビットを保持
random_bytes[8] |= 0x80; // バリアントを 10xx に設定
snprintf(uuid, size,
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
random_bytes[0], random_bytes[1], random_bytes[2], random_bytes[3],
random_bytes[4], random_bytes[5], random_bytes[6], random_bytes[7],
random_bytes[8], random_bytes[9], random_bytes[10], random_bytes[11],
random_bytes[12], random_bytes[13], random_bytes[14], random_bytes[15]);
}
// ファイルをアップロード
size_t read_file(const char *file_path, char **buffer) {
FILE *file = fopen(file_path, "rb");
if (!file) {
fprintf(stderr, "Cannot open file %s\n", file_path);
return 0; // 読み取り失敗
}
fseek(file, 0, SEEK_END);
size_t file_size = ftell(file);
fseek(file, 0, SEEK_SET);
*buffer = (char *)malloc(file_size);
if (!*buffer) {
fprintf(stderr, "Failed to allocate memory for file buffer\n");
fclose(file);
return 0; // 読み取り失敗
}
fread(*buffer, 1, file_size, file);
fclose(file);
return file_size; // 読み取ったバイト数を返す
}
// Authorization を計算
char* get_authorization(const char *http_method, const char *canonical_uri, const char *host,
const char *x_acs_action, const char *x_acs_version, const char *query_params,
const char *body, char *authorization_header,
char *hashed_payload, char *x_acs_date, char *uuid) {
// x-acs-signature-nonce、x-acs-date、x-acs-content-sha256、および署名する文字列を準備
generate_uuid(uuid, 37);
// x-acs-date の形式は yyyy-MM-ddTHH:mm:ssZ、例: 2025-04-17T07:19:10Z
time_t now = time(NULL);
struct tm *utc_time = gmtime(&now);
strftime(x_acs_date, 64, "%Y-%m-%dT%H:%M:%SZ", utc_time);
// 署名する文字列
char signed_headers[] = "host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version";
// x-acs-content-sha256
sha256_hex(body ? body : "", hashed_payload);
printf("Generated x-acs-content-sha256: %s\n", hashed_payload);
// 1. 正規リクエストヘッダーを構築
char canonical_headers[BUFFER_SIZE];
snprintf(canonical_headers, sizeof(canonical_headers),
"host:%s\nx-acs-action:%s\nx-acs-content-sha256:%s\nx-acs-date:%s\nx-acs-signature-nonce:%s\nx-acs-version:%s",
host, x_acs_action, hashed_payload, x_acs_date, uuid, x_acs_version);
printf("Canonical Headers:\n%s\n", canonical_headers);
// 2. 署名するリクエストヘッダーを構築
// クエリパラメーターをソートしてエンコード
char* sorted_query_params = generate_sorted_encoded_query(query_params);
if (!sorted_query_params) {
fprintf(stderr, "Failed to generate sorted query string\n");
return NULL;
}
char canonical_request[BUFFER_SIZE];
snprintf(canonical_request, sizeof(canonical_request),
"%s\n%s\n%s\n%s\n\n%s\n%s",
http_method,
canonical_uri,
sorted_query_params ? sorted_query_params : "",
canonical_headers,
signed_headers,
hashed_payload);
printf("Canonical Request:\n%s\n", canonical_request);
// 3. 正規リクエストの SHA-256 ハッシュを計算
char hashed_canonical_request[SHA256_DIGEST_LENGTH * 2 + 1];
sha256_hex(canonical_request, hashed_canonical_request);
printf("hashedCanonicalRequest: %s\n", hashed_canonical_request);
// 4. 署名文字列を構築
char string_to_sign[BUFFER_SIZE];
snprintf(string_to_sign, sizeof(string_to_sign), "%s\n%s", ALGORITHM, hashed_canonical_request);
printf("stringToSign:\n%s\n", string_to_sign);
// 5. 署名を計算
char signature[SHA256_DIGEST_LENGTH * 2 + 1];
hmac256(ACCESS_KEY_SECRET, string_to_sign, signature);
printf("Signature: %s\n", signature);
// 6. Authorization ヘッダーを構築
snprintf(authorization_header, BUFFER_SIZE,
"%s Credential=%s,SignedHeaders=%s,Signature=%s",
ALGORITHM, ACCESS_KEY_ID, signed_headers, signature);
printf("Authorization: %s\n", authorization_header);
return sorted_query_params;
}
// リクエストを送信
void call_api(const char *http_method, const char *canonical_uri, const char *host,
const char *x_acs_action, const char *x_acs_version, const char *query_params,
const char *body,const char *content_type, size_t body_length) {
// 署名計算に必要なパラメーター値を取得
char authorization_header[BUFFER_SIZE];
char hashed_payload[SHA256_DIGEST_LENGTH * 2 + 1];
char x_acs_date[64];
char uuid[37];
// 1. curl を初期化
CURL *curl = curl_easy_init();
if (!curl) {
fprintf(stderr, "curl_easy_init() failed\n");
goto cleanup;
}
// 2. 署名を計算 (ソートおよびエンコードされたクエリパラメーターを返す)
char *signed_query_params = get_authorization(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, authorization_header, hashed_payload, x_acs_date, uuid);
// 3. リクエストパラメーターを追加
char url[BUFFER_SIZE];
if (signed_query_params && strlen(signed_query_params) > 0) {
snprintf(url, sizeof(url), "https://%s%s?%s", host, canonical_uri, signed_query_params);
} else {
snprintf(url, sizeof(url), "https://%s%s", host, canonical_uri);
}
printf("Request URL: %s\n", url);
// メモリを解放
if (signed_query_params) {
free(signed_query_params); // メモリを解放
}
// 4. ヘッダーを追加
struct curl_slist *headers = NULL;
char header_value[BUFFER_SIZE];
snprintf(header_value, sizeof(header_value), "Content-Type: %s", content_type);
headers = curl_slist_append(headers, header_value);
snprintf(header_value, sizeof(header_value), "Authorization: %s", authorization_header);
headers = curl_slist_append(headers, header_value);
snprintf(header_value, sizeof(header_value), "host: %s", host);
headers = curl_slist_append(headers, header_value);
snprintf(header_value, sizeof(header_value), "x-acs-action: %s", x_acs_action);
headers = curl_slist_append(headers, header_value);
snprintf(header_value, sizeof(header_value), "x-acs-content-sha256: %s", hashed_payload);
headers = curl_slist_append(headers, header_value);
snprintf(header_value, sizeof(header_value), "x-acs-date: %s", x_acs_date);
headers = curl_slist_append(headers, header_value);
snprintf(header_value, sizeof(header_value), "x-acs-signature-nonce: %s", uuid);
headers = curl_slist_append(headers, header_value);
snprintf(header_value, sizeof(header_value), "x-acs-version: %s", x_acs_version);
headers = curl_slist_append(headers, header_value);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, http_method);
curl_easy_setopt(curl, CURLOPT_URL, url);
// その他の CURL 設定: SSL 検証を無効化、デバッグ情報を追加
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
// 5. ボディを追加
if (body) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body_length);
if (strcmp(content_type, "application/octet-stream") == 0) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
} else if (strcmp(content_type, "application/x-www-form-urlencoded") == 0) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
} else if (strcmp(content_type, "application/json; charset=utf-8") == 0) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
}
}
printf("RequestBody:%s\n",body);
// 6. リクエストを送信
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
goto cleanup;
}
cleanup:
if (headers) curl_slist_free_all(headers);
if (curl) curl_easy_cleanup(curl);
}
/**
*
* 署名の例。必要に応じて main メソッドの例のパラメーターを置き換えてください。
* <p>
* API メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得します。
* 1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、query_params を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
* 2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/octet-stream または application/json です。RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
* 2. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/x-www-form-urlencoded です。
*/
int main() {
// 応答形式を UTF-8 に設定
SetConsoleOutputCP(CP_UTF8);
srand((unsigned int)time(NULL));
/**
* RPC API リクエストの例: リクエストパラメーターは "query" にあり、クエリタイプは複雑です。
*/
const char *http_method = "POST";
const char *canonical_uri = "/";
const char *host = "tds.cn-shanghai.aliyuncs.com";
const char *x_acs_action = "AddAssetSelectionCriteria";
const char *x_acs_version = "2018-12-03";
// パラメーター SelectionKey を定義、文字列型
const char *selection_key = "85a561b7-27d5-47ad-a0ec-XXXXXXXX";
// パラメーター TargetOperationList を定義、ターゲットオブジェクトのコレクション (拡張可能)
struct {
const char *operation;
const char *target;
} targetOperation_list[] = {
{"add", "i-2ze1j7ocdg9XXXXXXXX"},
// さらにエントリを追加可能
// {"add", "i-abc123xyzXXXXX"},
};
int count = sizeof(targetOperation_list) / sizeof(targetOperation_list[0]);
KeyValuePair pairs[100]; // 元のキーと値を保存
int pair_count = 0;
for (int i = 0; i < count; ++i) {
char op_key[128], target_key[128];
snprintf(op_key, sizeof(op_key), "TargetOperationList.%d.Operation", i + 1);
snprintf(target_key, sizeof(target_key), "TargetOperationList.%d.Target", i + 1);
strncpy(pairs[pair_count].key, op_key, sizeof(pairs[pair_count].key));
strncpy(pairs[pair_count].value, targetOperation_list[i].operation, sizeof(pairs[pair_count].value));
pair_count++;
strncpy(pairs[pair_count].key, target_key, sizeof(pairs[pair_count].key));
strncpy(pairs[pair_count].value, targetOperation_list[i].target, sizeof(pairs[pair_count].value));
pair_count++;
}
// SelectionKey パラメーターを追加
snprintf(pairs[pair_count].key, sizeof(pairs[pair_count].key), "SelectionKey");
snprintf(pairs[pair_count].value, sizeof(pairs[pair_count].value), "%s", selection_key);
pair_count++;
// ソートとエンコーディングは get_authorization() で行われます
qsort(pairs, pair_count, sizeof(KeyValuePair), compare_pairs);
// 元のクエリ文字列を構築 (エンコードなし)
char query_params[BUFFER_SIZE] = {0};
for (int i = 0; i < pair_count; ++i) {
if (i == 0) {
snprintf(query_params, sizeof(query_params), "%s=%s", pairs[i].key, pairs[i].value);
} else {
char temp[512];
snprintf(temp, sizeof(temp), "&%s=%s", pairs[i].key, pairs[i].value);
strncat(query_params, temp, sizeof(query_params) - strlen(query_params) - 1);
}
}
const char *body = ""; // リクエストボディは空
const char *content_type = "application/json; charset=utf-8";
call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
/**
* RPC API リクエストの例: リクエストパラメーターは "query" にあります
*/
// API リクエストパラメーターを定義
// const char *http_method = "POST";
// const char *canonical_uri = "/";
// const char *host = "ecs.cn-hangzhou.aliyuncs.com";
// const char *x_acs_action = "DescribeInstanceStatus";
// const char *x_acs_version = "2014-05-26";
// // パラメーター InstanceId を定義、配列。InstanceId はオプションのパラメーターです。
// const char *instance_ids[] = {
// "i-bp11ht4hXXXXXXXX",
// "i-bp16maz3XXXXXXXX"
// };
// // InstanceId 配列を連結
// char InstanceId[BUFFER_SIZE];
// snprintf(InstanceId, sizeof(InstanceId),
// "InstanceId.1=%s&InstanceId.2=%s",
// instance_ids[0],
// instance_ids[1]);
// // クエリパラメーターを定義。必須パラメーター: RegionId=cn-hangzhou。const char *query_params = "RegionId=cn-hangzhou";
// char query_params[BUFFER_SIZE];
// snprintf(query_params, sizeof(query_params),
// "%s&RegionId=cn-hangzhou", InstanceId);
// const char *body = "";
// const char *content_type = "application/json; charset=utf-8";
// call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
/**
* RPC API リクエストの例: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)
*/
// 読み取ったファイルコンテンツを保存するためのポインターを宣言
// char *body = NULL;
// size_t body_length = read_file("<YOUR_FILE_PATH>", &body);
// if (body_length > 0) {
// const char *http_method = "POST";
// const char *canonical_uri = "/";
// const char *host = "ocr-api.cn-hangzhou.aliyuncs.com";
// const char *x_acs_action = "RecognizeGeneral";
// const char *x_acs_version = "2021-07-07";
// const char *query_params = "";
// const char *content_type = "application/octet-stream";
// call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, body_length);
// free(body);
// } else {
// fprintf(stderr, "File read error\n");
// }
/**
* RPC API リクエストの例: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)
*/
// const char *http_method = "POST";
// const char *canonical_uri = "/";
// const char *host = "mt.aliyuncs.com";
// const char *x_acs_action = "TranslateGeneral";
// const char *x_acs_version = "2018-10-12";
// char query_params[BUFFER_SIZE];
// snprintf(query_params, sizeof(query_params), "Context=%s", "Morning");
// const char *format_type = "text";
// const char *source_language = "zh";
// const char *target_language = "en";
// const char *source_text = "Hello";
// const char *scene = "general";
// char body[BUFFER_SIZE];
// snprintf(body, sizeof(body),
// "FormatType=%s&SourceLanguage=%s&TargetLanguage=%s&SourceText=%s&Scene=%s",
// percentEncode(format_type), percentEncode(source_language), percentEncode(target_language),
// percentEncode(source_text), percentEncode(scene));
// const char *content_type = "application/x-www-form-urlencoded";
// printf("formdate_body: %s\n", body);
// call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
// RPC API リクエストの例 3: リクエストパラメーターは "formData" にあります
// const char *http_method = "POST";
// const char *canonical_uri = "/";
// const char *host = "sasti.aliyuncs.com";
// const char *x_acs_action = "AskTextToTextMsg";
// const char *x_acs_version = "2020-05-12";
// // クエリ
// const char *query_params = "";
// // ボディ
// const char *Memory = "false";
// const char *Stream = "true";
// const char *ProductCode = "sddp_pre";
// const char *Feature = "{}";
// const char *Model = "yunsec-llm-latest";
// const char *Type = "Chat";
// const char *TopP = "0.9";
// const char *Temperature = "0.01";
// const char *Prompt = "Who are you";
// const char *Application = "sddp_pre";
// char body[BUFFER_SIZE];
// snprintf(body, sizeof(body),
// "Memory=%s&Stream=%s&ProductCode=%s&Feature=%s&Model=%s&Type=%s&TopP=%s&Temperature=%s&Prompt=%s&Application=%s",
// Memory, Stream, ProductCode, Feature, Model, Type, TopP, Temperature, Prompt, Application);
// const char *content_type = "application/x-www-form-urlencoded";
// printf("formdate_body: %s\n", body);
// call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
/**
* ROA API POST リクエスト "in" "body"
*/
// const char *http_method = "POST";
// const char *canonical_uri = "/clusters";
// const char *host = "cs.cn-beijing.aliyuncs.com";
// const char *x_acs_action = "CreateCluster";
// const char *x_acs_version = "2015-12-15";
// const char *query_params = "";
// char body[BUFFER_SIZE];
// snprintf(body, sizeof(body),
// "{\"name\":\"%s\",\"region_id\":\"%s\",\"cluster_type\":\"%s\","
// "\"vpcid\":\"%s\",\"container_cidr\":\"%s\","
// "\"service_cidr\":\"%s\",\"security_group_id\":\"%s\","
// "\"vswitch_ids\":[\"%s\"]}",
// "Test Cluster", "cn-beijing", "ExternalKubernetes",
// "vpc-2zeou1uod4yXXXXXXXX", "10.X.X.X/XX",
// "10.X.X.X/XX", "sg-2ze1a0rlgeXXXXXXXX",
// "vsw-2zei30dhflXXXXXXXX");
// const char *content_type = "application/json; charset=utf-8";
// call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
/**
* ROA API GET リクエスト
*/
// const char *http_method = "GET";
// char canonical_uri[BUFFER_SIZE];
// snprintf(canonical_uri, sizeof(canonical_uri), "/clusters/%s/resources", percentEncode("cd1f5ba0dbfa144XXXXXXXX"));
// const char *host = "cs.cn-beijing.aliyuncs.com";
// const char *x_acs_action = "DescribeClusterResources";
// const char *x_acs_version = "2015-12-15";
// const char *query_params = "with_addon_resources=true";
// const char *body = "";
// const char *content_type = "";
// call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
/**
* ROA API DELETE リクエスト
*/
// const char *http_method = "DELETE";
// char canonical_uri[BUFFER_SIZE];
// snprintf(canonical_uri, sizeof(canonical_uri), "/clusters/%s", percentEncode("cd1f5ba0dbfa144XXXXXXXX"));
// const char *host = "cs.cn-beijing.aliyuncs.com";
// const char *x_acs_action = "DeleteCluster";
// const char *x_acs_version = "2015-12-15";
// const char *query_params = "";
// const char *body = "";
// const char *content_type = "";
// call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
// 生成された値を保存する変数
char authorization_header[BUFFER_SIZE];
char hashed_payload[SHA256_DIGEST_LENGTH * 2 + 1];
char x_acs_date[64];
char uuid[37];
return 0;
}
よくある質問
署名が失敗した場合に「Specified signature does not match our calculation.」または「The request signature does not conform to Aliyun standards.」というエラーが表示されるのはなぜですか?
Postman でテストするにはどうすればよいですか?
リクエストパラメーターを渡すにはどうすればよいですか?
API メタデータの style の値が RPC または ROA ではない場合、API スタイルはどのように決定されますか?
配列型またはオブジェクト型のパラメーターを渡すにはどうすればよいですか?
API バージョン (x-acs-version) を取得する方法
自己署名時に GET で正常にデバッグできる場合、POST を使用できますか。
API の呼び出し時に「You are not authorized to do this operation.」というエラーが表示されるのはなぜですか?
AccessKey ペアを取得するにはどうすればよいですか?
お問い合わせ
署名の計算時に解決できない問題が発生した場合は、ID 147535001692 の DingTalk グループに参加して、担当エンジニアに支援を依頼してください。
署名の計算に関係のない問題については、このグループに参加しないでください。参加した場合、効果的な応答が得られない可能性があります。


