當您不希望通過SDK調用阿里雲OpenAPI,或您的程式運行環境不支援使用SDK時,您可以採用自簽名的方式來調用阿里雲OpenAPI。本文將通過介紹V3版本的簽名機制,協助您實現直接使用HTTP請求調用阿里雲OpenAPI。
使用說明
HTTP 要求結構
一個完整的阿里雲OpenAPI請求,包含以下部分。
名稱 | 是否必選 | 描述 | 樣本值 |
協議 | 是 | 您可以查閱不同雲產品的 API 參考文檔進行配置。支援通過 | https:// |
服務地址 | 是 | 即 Endpoint。您可以查閱不同雲產品的服務接入地址文檔,瞭解不同服務地區下的服務地址。 | ecs.cn-shanghai.aliyuncs.com |
resource_URI_parameters | 是 | 介面URL,包括介面路徑和位置在 path、 query的介面請求參數。 | ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai |
RequestHeader | 是 | 公用要求標頭資訊,通常包含API的版本、Host、Authorization等資訊。後文將詳細說明。 | 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 | 是 | 定義在body中的業務請求參數,可通過OpenAPI中繼資料擷取。 | |
HTTPMethod | 是 | 要求方法,可通過OpenAPI中繼資料擷取。 | POST |
RequestHeader
調用阿里雲OpenAPI時,公用要求標頭需要包含如下資訊。
名稱 | 類型 | 是否必選 | 描述 | 樣本值 |
host | String | 是 | 即服務地址,參見HTTP 要求結構。 | ecs.cn-shanghai.aliyuncs.com |
x-acs-action | String | 是 | API的名稱。您可以訪問阿里雲 OpenAPI 開發人員門戶,搜尋您想調用的 OpenAPI。 | RunInstances |
x-acs-content-sha256 | String | 是 | RequestBody經過Hash摘要處理後再進行Base16編碼的結果,與HashedRequestPayload相一致。 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
x-acs-date | String | 是 | 按照ISO 8601標準表示的UTC時間,格式為yyyy-MM-ddTHH:mm:ssZ,例如2018-01-01T12:00:00Z。值為請求發出前15分鐘內的時間。 | 2023-10-26T10:22:32Z |
x-acs-signature-nonce | String | 是 | 簽名唯一隨機數。該隨機數旨在防止網路重放攻擊,每一次請求均需使用不同的隨機數。此機制僅適用於HTTP協議。 | 3156853299f313e23d1673dc12e1703d |
x-acs-version | String | 是 | API 版本。如何擷取請參見API版本(x-acs-version)如何擷取。 | 2014-05-26 |
Authorization | String | 非匿名請求必須 | 用於驗證請求合法性的認證資訊,格式為Authorization: SignatureAlgorithm Credential=AccessKeyId,SignedHeaders=SignedHeaders,Signature=Signature。 其中SignatureAlgorithm為簽名加密方式,為ACS3-HMAC-SHA256。 Credential 為使用者的存取金鑰ID。您可以在RAM 控制台查看您的 AccessKeyId。如需建立 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 | String | STS認證必傳 | 為調用AssumeRole介面傳回值中SecurityToken的值。 |
簽名機制
採用AK/SK方式進行簽名與認證。對於每一次HTTP或HTTPS協議請求,阿里雲API Gateway將依據請求參數資訊重新計算簽名,通過對比該簽名與請求中提供的簽名是否一致,從而驗證要求者的身份,以確保傳輸資料的完整性與安全性。
請求及返回結果都使用UTF-8字元集進行編碼。
步驟一:構造正常化請求
構造正常化請求(CanonicalRequest)的虛擬碼如下:
CanonicalRequest =
HTTPRequestMethod + '\n' + // http要求方法,全大寫
CanonicalURI + '\n' + // 正常化URI
CanonicalQueryString + '\n' + // 正常化查詢字串
CanonicalHeaders + '\n' + // 正常化訊息頭
SignedHeaders + '\n' + // 已簽名訊息頭
HashedRequestPayload // RequestBody經過hash處理後的值HTTPRequestMethod(要求方法)
即大寫的HTTP方法名,如GET、POST。
CanonicalURI(正常化URI)
URL的資源路徑部分經過編碼之後的結果。資源路徑部分指URL中host與查詢字串之間的部分,包含host之後的/但不包含查詢字串前的?。使用者發起請求時的URI應使用正常化URI,編碼方式使用UTF-8字元集按照RFC3986的規則對URI中的每一部分(即被/分割開的字串)進行編碼:
字元A~Z、a~z、0~9以及字元
-、_、.、~不編碼。其他字元編碼成
%加字元對應ASCII碼的16進位。樣本:半形雙引號(")對應%22。需要注意的是,部分特殊字元需要特殊處理,具體如下:編碼前
編碼後
空格( )
%20星號(
*)%2A%7E波浪號(
~)
如果您使用的是Java標準庫中的java.net.URLEncoder,可以先用標準庫中encode編碼,隨後將編碼後的字元中加號(+)替為%20、星號(*)替換為%2A、%7E替換為波浪號(~),即可得到上述規則描述的編碼字串。
RPC風格API使用正斜杠(/)作為CanonicalURI,
ROA風格API該參數為OpenAPI中繼資料中path的值,例如/api/v1/clusters。
CanonicalQueryString(正常化查詢字串)
在OpenAPI中繼資料中,如果API的請求參數資訊包含了"in":"query"時,需要將這些請求參數按照如下構造方法拼接起來:
將請求參數按照參數名稱的字元順序升序排列。
使用UTF-8字元集按照RFC3986的規則對每個參數的參數名稱和參數值分別進行URI編碼,具體規則與上一節中的CanonicalURI編碼規則相同。
使用等號(
=)串連編碼後的請求參數名稱和參數值。當參數沒有值時,應使用Null 字元串作為該參數的值。多個請求參數之間使用與號(
&)串連。
當請求參數是JSON字串類型時,JSON字串中的參數順序不會影響簽名計算。
當無查詢字串時,使用Null 字元串作為正常化查詢字串。
樣本值:
ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghaiHashedRequestPayload
RequestBody經過Hash摘要處理後再進行Base16編碼得到HashedRequestPayload,並將RequestHeader中x-acs-content-sha256的值更新為HashedRequestPayload的值。虛擬碼如下:
HashedRequestPayload = HexEncode(Hash(RequestBody))在OpenAPI中繼資料中,如果API的請求參數資訊包含了
"in": "body"或"in": "formData"時,需通過RequestBody傳遞參數:說明若無請求參數通過RequestBody傳遞時,RequestBody的值固定為一個Null 字元串。
當請求參數資訊包含
"in": "formData"時,參數需按照key1=value1&key2=value2&key3=value3拼接成字串,同時,需在RequestHeader中添加content-type=application/x-www-form-urlencoded。需要注意的是,當請求參數類型是array、object時,需要將參數值轉化為帶索引的索引值對。當請求參數資訊包含
"in": "body"時,需要在RequestHeader中添加content-type,content-type的值與請求內容類型有關。例如:請求內容類型為JSON資料時,content-type的值為
application/json。請求內容類型為二進位檔案流時,content-type的值為
application/octet-stream。
Hash表示訊息摘要函數,目前僅支援SHA256演算法。
HexEncode表示以小寫十六進位的形式返回摘要的編碼函數(即Base16編碼)。
當RequestBody為空白時,HashedRequestPayload的樣本值:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855CanonicalHeaders(正常化要求標頭)
將RequestHeader中的參數按照如下構造方法拼接起來:
過濾出RequestHeader中包含以
x-acs-為首碼、host、content-type的參數。將參數的名稱轉換為小寫,並按照字元順序升序排列。
將參數的值去除首尾空格。
將參數名稱和參數值以英文冒號(
:)串連,並在尾部添加分行符號(\n),組成一個正常化訊息頭(CanonicalHeaderEntry)。將多個正常化訊息頭(CanonicalHeaderEntry)拼接成一個字串。
除Authorization外的所有RequestHeader,只要符合要求都必須被加入簽名。
虛擬碼如下:
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(已簽名訊息頭列表)
用於說明此次請求中參與簽名的公用要求標頭資訊,與CanonicalHeaders中的參數名稱一一對應。其構造方法如下:
將CanonicalHeaders中包含的要求標頭的名稱轉為小寫。
按首字母升序排列並以英文分號(
;)分隔。
虛擬碼如下:
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步驟二:構造待簽名字串
按照以下虛擬碼構造待簽名字串(stringToSign):
StringToSign =
SignatureAlgorithm + '\n' +
HashedCanonicalRequestSignatureAlgorithm
簽名協議目前僅支援ACS3-HMAC-SHA256演算法。
HashedCanonicalRequest
正常化請求摘要串,計算方法虛擬碼如下:
HashedCanonicalRequest = HexEncode(Hash(CanonicalRequest))Hash表示訊息摘要函數,目前僅支援SHA256演算法。
HexEncode表示以小寫十六進位的形式返回摘要的編碼函數(即Base16編碼)。
樣本值:
ACS3-HMAC-SHA256
7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259步驟三:計算簽名
按照以下虛擬碼計算簽名值(Signature)。
Signature = HexEncode(SignatureMethod(Secret, StringToSign))StringToSign:步驟二中構造的待簽名字串,UTF-8編碼。
SignatureMethod:使用HMAC-SHA256作為簽名演算法。
Secret:AccessKey Secret。
HexEncode:以小寫十六進位的形式返回摘要的編碼函數(即Base16編碼)。
樣本值:
06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0步驟四:將簽名添加到請求中
計算完簽名後,構造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簽名範例程式碼
為了讓您更清晰地理解上述簽名機制,下面以主流程式設計語言為例,將簽名機制完整實現。範例程式碼只是讓您更好地理解簽名機制,存在不通用性,阿里雲OpenAPI提供多種程式設計語言和開發架構的SDK,使用這些SDK可以免去簽名過程,便於您快速構建與阿里雲相關的應用程式,建議您使用SDK。
在簽名之前,請務必要先查看OpenAPI中繼資料,擷取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樣本
範例程式碼的運行環境是JDK1.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 Method
private final String httpMethod;
// 請求路徑
private final String canonicalUri;
// endpoint
private final String host;
// API name
private final String xAcsAction;
// API version
private final String xAcsVersion;
// headers
private final Map<String, String> headers = new TreeMap<>();
// body參數
private byte[] body;
// query參數
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方法中的樣本參數。
* ROA介面和RPC介面只有canonicalUri取值邏輯是完全不同,其餘內容都是相似的。
* <p>
* 通過API中繼資料擷取要求方法(methods)、請求參數名稱(name)、請求參數類型(type)、請求參數位置(in),並將參數封裝到SignatureRequest中。
* 1. 請求參數在中繼資料中顯示"in":"query",通過queryParam傳參。註:RPC介面該型別參數也支援通過body傳參,content-type為application/x-www-form-urlencoded,參見樣本三。
* 2. 請求參數在中繼資料中顯示"in": "body",通過body傳參,MIME類型為application/octet-stream、application/json。註:RPC介面不建議使用application/json,可使用樣本三替代。
* 3. 請求參數在中繼資料中顯示"in": "formData",通過body傳參,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("請設定環境變數 ALIBABA_CLOUD_ACCESS_KEY_ID 和 ALIBABA_CLOUD_ACCESS_KEY_SECRET");
return;
}
// RPC介面樣本一:請求參數"in":"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介面樣本二:請求參數"in":"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介面樣本三:請求參數"in": "formData"或"in":"body"(非上傳檔案情境),以機器翻譯的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", "你好");
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介面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", "測試");
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介面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介面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版本在終端(Terminal)執行以下命令。
Python3
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選擇樣本一即可,然後修改http_method、host、x_acs_action、x_acs_version以及query_param。
ROA和RPC只有canonicalUri取值邏輯是不同的。
通過OpenAPI中繼資料擷取要求方法(methods)、請求參數名稱(name)、請求參數類型(type)、請求參數位置(in),並將參數封裝到SignatureRequest中。
1. 請求參數在中繼資料中顯示"in":"query",通過queryParam傳參,無需設定content-type。註:RPC介面該型別參數也支援通過body傳參,content-type為application/x-www-form-urlencoded,參見樣本三。
2. 請求參數在中繼資料中顯示"in": "body",通過body傳參,根據實際情況設定content-type。註:RPC介面不建議使用application/json,可使用樣本三替代。
3. 請求參數在中繼資料中顯示"in": "formData",通過body傳參,content-type為application/x-www-form-urlencoded。
"""
if __name__ == "__main__":
# RPC介面請求樣本一:請求參數"in":"query"
http_method = "POST" # 請求方式,從中繼資料中可以擷取,建議使用POST。
canonical_uri = "/" # RPC介面無資源路徑,故使用正斜杠(/)作為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介面請求樣本二:請求參數"in":"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",通過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介面請求樣本三:請求參數"in": "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'] = '早上'
# # FormatType、SourceLanguage、TargetLanguage等參數,在中繼資料中顯示"in":"formData"
# form_data = OrderedDict()
# form_data["FormatType"] = "text"
# form_data["SourceLanguage"] = "zh"
# form_data["TargetLanguage"] = "en"
# form_data["SourceText"] = "你好"
# 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"
# # 樣本四:ROA介面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傳參。
# 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"
# # 樣本五:ROA介面GET請求
# http_method = "GET"
# # canonicalUri如果存在path參數,需要對path參數encode,percent_code({path參數})
# 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
# # 樣本六:ROA介面DELETE請求
# http_method = "DELETE"
# # canonicalUri如果存在path參數,需要對path參數encode,percent_code({path參數})
# 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,您可能需要根據具體情況對代碼進行相應的調整。
需要您在終端(Terminal)執行以下命令:
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方法中的樣本參數。
// ROA介面和RPC介面只有canonicalUri取值邏輯是完全不同,其餘內容都是相似的。
// 通過API中繼資料擷取要求方法(methods)、請求參數名稱(name)、請求參數類型(type)、請求參數位置(in),並將參數封裝到SignatureRequest中。
// 1. 請求參數在中繼資料中顯示"in":"query",通過queryParam傳參。註:RPC介面該型別參數也支援通過body傳參,content-type為application/x-www-form-urlencoded,參見樣本三。
// 2. 請求參數在中繼資料中顯示"in": "body",通過body傳參,MIME類型為application/octet-stream、application/json。RPC介面不建議使用application/json,可使用樣本三替代。
// 3. 請求參數在中繼資料中顯示"in": "formData",通過body傳參,MIME類型為application/x-www-form-urlencoded。
func main() {
// RPC介面請求樣本一:請求參數"in":"query"
httpMethod := "POST" // 請求方式,大部分RPC介面同時支援POST和GET,此處以POST為例
canonicalUri := "/" // RPC介面無資源路徑,故使用正斜杠(/)作為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介面請求樣本二:請求參數"in":"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介面請求樣本三:請求參數"in": "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"] = "早上"
// // FormatType、SourceLanguage、TargetLanguage等參數,在中繼資料中顯示"in":"formData"
// body := make(map[string]interface{})
// body["FormatType"] = "text"
// body["SourceLanguage"] = "zh"
// body["TargetLanguage"] = "en"
// body["SourceText"] = "你好"
// body["Scene"] = "general"
// str := formDataToString(body)
// req.body = []byte(*str)
// req.headers["content-type"] = "application/x-www-form-urlencoded"
// // ROA介面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中
// 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介面GET請求
// httpMethod := "GET"
// // canonicalUri如果存在path參數,需要對path參數encode,percentCode({path參數})
// 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介面DELETE請求
// httpMethod := "DELETE"
// // canonicalUri如果存在path參數,需要對path參數encode,percentCode({path參數})
// 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雜湊值並轉換為小寫十六進位字串
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;
}
// 將所有key都轉換為小寫
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) {
// 如果值為空白,則無需進一步處理
if (value === null) {
return;
}
if (key === null) {
key = "";
}
// 當值為Array類型時,遍曆Array中的每個元素,並遞迴處理
if (Array.isArray(value)) {
value.forEach((item, index) => {
processObject(map, `${key}.${index + 1}`, item);
});
} else if (typeof value === 'object' && value !== null) {
// 當值為Object類型時,遍曆Object中的每個索引值對,並遞迴處理
Object.entries(value).forEach(([subKey, subValue]) => {
processObject(map, `${key}.${subKey}`, subValue);
});
} else {
// 對於以"."開頭的鍵,移除開頭的"."以保持鍵的連續性
if (key.startsWith('.')) {
key = key.slice(1);
}
map[key] = String(value);
}
}
/**
* 簽名樣本,您需要根據實際情況替換main方法中的樣本參數。
* ROA介面和RPC介面只有canonicalUri取值邏輯是完全不同,其餘內容都是相似的。
*
* 通過API中繼資料擷取要求方法(methods)、請求參數名稱(name)、請求參數類型(type)、請求參數位置(in),並將參數封裝到SignatureRequest中。
* 1. 請求參數在中繼資料中顯示"in":"query",通過queryParam傳參。註:RPC介面該型別參數也支援通過body傳參,content-type為application/x-www-form-urlencoded,參見樣本三。
* 2. 請求參數在中繼資料中顯示"in": "body",通過body傳參,MIME類型為application/octet-stream、application/json。RPC介面不建議使用application/json,可使用樣本三替代。
* 3. 請求參數在中繼資料中顯示"in": "formData",通過body傳參,MIME類型為application/x-www-form-urlencoded。
*/
// RPC介面請求樣本一:請求參數"in":"query"
const httpMethod = 'POST'; // 請求方式,大部分RPC介面同時支援POST和GET,此處以POST為例
const canonicalUri = '/'; // RPC介面無資源路徑,故使用正斜杠(/)作為CanonicalURI
const host = 'ecs.cn-hangzhou.aliyuncs.com'; // endpoint
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介面請求樣本二:請求參數"in":"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",表示參數放在body中
// signRequest.body = bytes;
// signRequest.headers['content-type'] = 'application/octet-stream';
// // RPC介面請求樣本三:請求參數"in": "formData"或"in":"body"(非上傳檔案情境)
// const httpMethod = 'POST'; // 請求方式,大部分RPC介面同時支援POST和GET,此處以POST為例
// const canonicalUri = '/'; // RPC介面無資源路徑,故使用正斜杠(/)作為CanonicalURI
// const host = 'mt.aliyuncs.com'; // endpoint
// 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"] = "早上";
// // FormatType、SourceLanguage、TargetLanguage等參數,在中繼資料中顯示"in":"formData"
// const formData = {
// SourceLanguage: "zh",
// TargetLanguage: "en",
// FormatType: "text",
// Scene: "general",
// SourceText: '你好'
// }
// const str = formDataToString(formData)
// signRequest.body = encoder.encode(str);
// signRequest.headers['content-type'] = 'application/x-www-form-urlencoded';
// // ROA介面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",表示參數放在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介面GET請求
// const httpMethod = 'GET';
// // canonicalUri如果存在path參數,需要對path參數encode,percentCode({path參數})
// 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介面DELETE請求
// const httpMethod = 'DELETE';
// // canonicalUri如果存在path參數,需要對path參數encode,percentCode({path參數})
// 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方法中的樣本參數。
* ROA介面和RPC介面只有canonicalUri取值邏輯是完全不同,其餘內容都是相似的。
*
* 通過API中繼資料擷取要求方法(methods)、請求參數名稱(name)、請求參數類型(type)、請求參數位置(in),並將參數封裝到SignatureRequest中。
* 1. 請求參數在中繼資料中顯示"in":"query",通過queryParam傳參。註:RPC介面該型別參數也支援通過body傳參,content-type為application/x-www-form-urlencoded,參見樣本三。
* 2. 請求參數在中繼資料中顯示"in": "body",通過body傳參,MIME類型為application/octet-stream、application/json。RPC介面不建議使用application/json,可使用樣本三替代。
* 3. 請求參數在中繼資料中顯示"in": "formData",通過body傳參,MIME類型為application/x-www-form-urlencoded。
*/
public function main()
{
// RPC介面請求樣本一:請求參數"in":"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介面請求樣本二:請求參數"in":"body"(上傳檔案情境)
// $request = $this->createRequest('POST', '/', 'ocr-api.cn-hangzhou.aliyuncs.com', 'RecognizeGeneral', '2021-07-07');
// // 請求參數在中繼資料中顯示"in": "body",通過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介面請求樣本三:請求參數"in": "formData"或"in":"body"(非上傳檔案情境)
// $request = $this->createRequest('POST', '/', 'mt.aliyuncs.com', 'TranslateGeneral', '2018-10-12');
// // TranslateGeneral請求參數如下:
// $request['queryParam'] = [
// // Context在中繼資料中顯示的類型是String,"in":"query",非必填
// 'Context' => '早上',
// ];
// $formData = [
// 'FormatType' => 'text',
// 'SourceLanguage' => 'zh',
// 'TargetLanguage' => 'en',
// 'SourceText' => '你好',
// 'Scene' => 'general',
// ];
// $str = self::formDataToString($formData);
// $request['body'] = $str;
// $request['headers']['content-type'] = 'application/x-www-form-urlencoded';
// // ROA介面POST請求
// $request = $this->createRequest('POST', '/clusters', 'cs.cn-beijing.aliyuncs.com', 'CreateCluster', '2015-12-15');
// $bodyData = [
// 'name' => '測試叢集',
// '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介面GET請求
// // canonicalUri如果存在path參數,需要對path參數encode,rawurlencode({path參數})
// $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介面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)
{
// 如果值為空白,則無需進一步處理
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);
// Build and encode query parameters
$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)
{
// Sort headers by key and concatenate them
uksort($headers, 'strcasecmp');
$canonicalHeaders = '';
foreach ($headers as $key => $value) {
$canonicalHeaders .= strtolower($key) . ':' . trim($value) . "\n";
}
return $canonicalHeaders;
}
private function buildSignedHeaders($headers)
{
// Build the signed headers string
$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("環境變數 ALIBABA_CLOUD_ACCESS_KEY_ID 未設定");
private static readonly string AccessKeySecret = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET") ?? throw new InvalidOperationException("環境變數 ALIBABA_CLOUD_ACCESS_KEY_SECRET 未設定");
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方法中的樣本參數。
* ROA介面和RPC介面只有canonicalUri取值邏輯是完全不同,其餘內容都是相似的。
*
* 通過API中繼資料擷取要求方法(methods)、請求參數名稱(name)、請求參數類型(type)、請求參數位置(in),並將參數封裝到SignatureRequest中。
* 1. 請求參數在中繼資料中顯示"in":"query",通過queryParam傳參。註:RPC介面該型別參數也支援通過body傳參,content-type為application/x-www-form-urlencoded,參見樣本三。
* 2. 請求參數在中繼資料中顯示"in": "body",通過body傳參,MIME類型為application/octet-stream、application/json。RPC介面不建議使用application/json,可使用樣本三替代。
* 3. 請求參數在中繼資料中顯示"in": "formData",通過body傳參,MIME類型為application/x-www-form-urlencoded。
*/
public static void Main(string[] args)
{
// RPC介面請求樣本一:請求參數"in":"query"
string httpMethod = "POST"; // 請求方式,大部分RPC介面同時支援POST和GET,此處以POST為例
string canonicalUri = "/"; // RPC介面無資源路徑,故使用正斜杠(/)作為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介面請求樣本二:請求參數"in":"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",通過body傳參。
// request.Body = File.ReadAllBytes(@"D:\test.png");
// request.Headers["content-type"] = "application/octet-stream";
// // RPC介面請求樣本三:請求參數"in": "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"] = "早上";
// // FormatType、SourceLanguage、TargetLanguage等參數,在中繼資料中顯示"in":"formData"
// var body = new Dictionary<string, object>
// {
// { "FormatType", "text" },
// { "SourceLanguage", "zh" },
// { "TargetLanguage", "en" },
// { "SourceText", "你好" },
// { "Scene", "general" },
// };
// var str = FormDataToString(body);
// request.Body = Encoding.UTF8.GetBytes(str);
// request.Headers[ContentType] = "application/x-www-form-urlencoded";
// // ROA介面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);
// // 請求body,通過JsonConvert將body轉成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介面GET請求
// String httpMethod = "GET";
// // canonicalUri如果存在path參數,需要對path參數encode,percentCode({path參數})
// 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介面DELETE請求
// String httpMethod = "DELETE";
// // canonicalUri如果存在path參數,需要對path參數encode,percentCode({path參數})
// 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("輸入字串不可為null或空");
}
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) // 返回一個 Cow<str> 可以持有 String 或 &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
}
/// 計算SHA256雜湊
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 類型資料的value類型
#[derive(Debug, Clone)]
pub enum FormValue {
String(String),
Vec(Vec<String>),
HashMap(HashMap<String, String>),
}
// 定義一個body請求體枚舉,用於統一處理請求體類型,包含Json/Binary/FormData類型
pub enum RequestBody {
Json(HashMap<String, Value>), // Json
Binary(Vec<u8>), // Binary
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 類型處理請求體內容,將處理後的儲存在 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(),
};
// 計算 請求體body的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)], // 接收 query 參數
body: &RequestBody, // 用此判斷 body 資料類型
body_content: &str, // body 不為空白時 接收 body 請求參數 FormData/Json/Binary
) -> Result<Response, String> {
let mut request_builder = client.request(method.clone(), url);
// 添加要求標頭 headers
for (k, v) in headers.iter() {
request_builder = request_builder.header(k, v.clone());
}
// 添加請求體 body
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(_) => {
// 如果body為map,且不為空白,轉化為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介面該型別參數也支援通過body傳參,content-type為application/x-www-form-urlencoded,參見樣本三。
* 2. 請求參數在中繼資料中顯示"in": "body",通過body傳參,MIME類型為application/octet-stream、application/json。RPC介面不建議使用application/json,可使用樣本三替代。
* 2. 請求參數在中繼資料中顯示"in": "formData",通過body傳參,,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介面請求樣本一:請求參數"in":"query" POST
let method = Method::POST; // 要求方法
let host = "ecs.cn-hangzhou.aliyuncs.com"; // endpoint
let canonical_uri = "/"; // RPC介面無資源路徑,故使用正斜杠(/)作為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));
}
// query 參數
let query_params: &[(&str, &str)] = &query;
// 請求體 body 為空白時
let body = RequestBody:: None;
// RPC介面 "in":"query" query為複雜類型參數 POST
// let method = Method::POST; // 要求方法
// let host = "tds.cn-shanghai.aliyuncs.com"; // endpoint
// let canonical_uri = "/"; // RPC介面無資源路徑,故使用正斜杠(/)作為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 ,集合中傳入map類型
// 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介面請求樣本二:請求參數"in":"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("讀檔案失敗"); // <FILE_PATH>需替換為實際檔案路徑
// 當 body 為 二進位類型時
// let body = RequestBody::Binary(binary_data);
// query 參數為空白
// let query_params = &[];
// RPC介面請求樣本三:請求參數"in": "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枚舉中添加
// 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("你好")));
// form_data.insert(String::from("Scene"),FormValue::String(String::from("general")));
// query 參數
// let query_params = &[("Context", "早上")];
// body 為 FormData 類型時 "in":"formdata"
// let body = RequestBody::FormData(form_data);
// ROA介面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()) // String Value::Number(serde_json::Number::from(42)) // Number Value::Bool(true) // Boolean Value::Null // Null Value::Array(vec![Value::from(1), Value::from(2), Value::from(3)]) //Array json!({"nested_key": "nested_value"})
// body_json.insert(String::from("name"),json!("測試叢集"));
// 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"),
// ]),
// );
// query 參數為空白
// let query_params = &[];
// body 為 Json 類型時
// let body = RequestBody::Json(body_json);
// ROA介面GET請求 API:DeleteCluster 查詢指定叢集的關聯資源
// let method = Method::GET; // 要求方法
// let host = "cs.cn-hangzhou.aliyuncs.com"; // endpoint
// // 拼接資源路徑
// 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版本號碼
// // 設定 query 參數
// let query_params = &[("with_addon_resources", if true { "true" } else { "false" })]; // "true" or "false"
// // 設定 body 參數為空白
// let body = RequestBody:: None;
// ROA介面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";
// // query 參數
// let query_params = &[];
// // body 參數為空白時
// let body = RequestBody:: None;
// 傳送簡訊 API
// let method = Method::POST; // 要求方法
// let host = "dysmsapi.aliyuncs.com"; // endpoint
// let canonical_uri = "/"; // RPC介面無資源路徑,故使用正斜杠(/)作為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>"));
// // query 參數
// let query_params: &[(&str, &str)] = &query;
// // 請求體 body 為空白時
// 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),
Err(error) => eprintln!("異常: {}", error),
}
}
Shell指令碼樣本
#!/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型別參數或者formdata型別參數通過body傳參
# body型別參數:body的值為json字串: "{'key1':'value1','key2':'value2'}",且需在簽名header中添加content-type:application/json; charset=utf-8
# body型別參數是二進位檔案時:body無需修改,只需在簽名header中添加content-type:application/octet-stream,並在curl_command中添加--data-binary參數
# formdata型別參數:body參數格式:"key1=value1&key2=value2",且需在簽名header中添加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')
# 簽名header
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型別參數是二進位檔案時,需要注釋掉下面這行代碼
curl_command+=" -d '$body'"
# body型別參數是二進位檔案時,需要放開下面這行代碼的注釋
#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;
// 比較函數:按 key 字典序排序
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, "輸入字串不可為null\n");
return NULL;
}
size_t len = strlen(str);
char* encoded = (char*)malloc(len * 3 + 1);
if (encoded == NULL) {
fprintf(stderr, "記憶體配置失敗\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(""); // 空參數返回Null 字元串
}
KeyValuePair pairs[100]; // 最多支援 100 個索引值對
int pair_count = 0;
char* copy = strdup(query_params);
if (!copy) {
fprintf(stderr, "記憶體配置失敗\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);
// 按 key 排序
qsort(pairs, pair_count, sizeof(KeyValuePair), compare_pairs);
// 拼接排序後的查詢字串
char* query_sorted = malloc(BUFFER_SIZE);
if (!query_sorted) {
fprintf(stderr, "記憶體配置失敗\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.構造待簽名的要求標頭
// 對query參數進行排序編碼處理
char* sorted_query_params = generate_sorted_encoded_query(query_params);
if (!sorted_query_params) {
fprintf(stderr, "產生排序後的查詢字串失敗\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.計算簽名 (返回已經排序編碼處理後的query參數)
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.添加headers要求標頭
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.添加 body 請求體
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介面該型別參數也支援通過body傳參,content-type為application/x-www-form-urlencoded,參見樣本三。
* 2. 請求參數在中繼資料中顯示"in": "body",通過body傳參,MIME類型為application/octet-stream、application/json。RPC介面不建議使用application/json,可使用樣本三替代。
* 2. 請求參數在中繼資料中顯示"in": "formData",通過body傳參,MIME類型為application/x-www-form-urlencoded。
*/
int main() {
// 設定響應格式為UTF-8
SetConsoleOutputCP(CP_UTF8);
srand((unsigned int)time(NULL));
/**
* RPC介面請求樣本:請求參數"in":"query" 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]; // 儲存原始 key 和 value
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);
// 構造原始 query string(未編碼)
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介面請求樣本:請求參數"in":"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介面請求樣本:請求參數"in":"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介面請求樣本:請求參數"in": "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", "早上");
// const char *format_type = "text";
// const char *source_language = "zh";
// const char *target_language = "en";
// const char *source_text = "你好";
// 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介面請求樣本三:請求參數"in": "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";
// // query
// const char *query_params = "";
// // body
// 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 = "你是誰";
// 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介面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\"]}",
// "測試叢集", "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介面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介面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時,如何確定風格?
當請求參數類型是array、object時,該如何傳參?
API版本(x-acs-version)如何擷取?
自簽名時,如果使用GET調試成功了,可以使用POST嗎?
調用API時提示“You are not authorized to do this operation. ”
如何擷取AccessKey?
聯絡我們
當您在計算簽名時遇到無法解決的問題,可以加入DingTalk群:147535001692,聯絡值班同學進行諮詢。
非簽名計算相關的問題,請勿加入此群,以免無法獲得有效回答。


