全部产品
Search
文档中心

Alibaba Cloud SDK:Sintaks Permintaan dan Metode Tanda Tangan V3

更新时间:Nov 10, 2025

Jika Anda tidak ingin menggunakan SDK untuk memanggil operasi API Alibaba Cloud atau lingkungan runtime Anda tidak mendukung SDK, Anda dapat menggunakan tanda tangan mandiri untuk memanggil operasi API. Topik ini menjelaskan metode tanda tangan V3 serta cara memanggil operasi API Alibaba Cloud menggunakan permintaan HTTP.

Catatan Penggunaan

  • Jika Anda masih menggunakan metode tanda tangan V2, disarankan untuk beralih ke metode tanda tangan V3.

  • Metode tanda tangan V3 dapat digunakan untuk memanggil operasi API layanan Alibaba Cloud yang menggunakan SDK yang disediakan oleh OpenAPI Explorer. Perhatikan bahwa beberapa layanan cloud menggunakan gerbang yang dikelola sendiri, yang mungkin menerapkan metode autentikasi berbeda dari yang dijelaskan dalam topik ini. Dalam kasus tersebut, kami menyarankan Anda membaca dokumentasi sintaks dan metode tanda tangan layanan terkait sebelum mengirim permintaan HTTP untuk memanggil operasi API.

Sintaks Permintaan HTTP

Tabel berikut menjelaskan komponen permintaan API Alibaba Cloud.

Komponen

Diperlukan

Deskripsi

Contoh

Protokol

Ya

Protokol yang digunakan untuk mengirim permintaan API. Anda dapat membaca referensi API dari setiap layanan Alibaba Cloud untuk mendapatkan informasi tentang protokol yang digunakan. Anda dapat mengirim permintaan melalui HTTP atau HTTPS. Untuk memastikan keamanan data, kami sarankan Anda mengirim permintaan melalui HTTPS. Nilai valid: https:// dan http://.

https://

Endpoint

Ya

Endpoint dari API layanan Alibaba Cloud. Anda dapat membaca referensi API dari setiap layanan Alibaba Cloud untuk melihat endpoint layanan di berbagai wilayah.

ecs.cn-shanghai.aliyuncs.com

resource_URI_parameters

Ya

URL dari sumber daya yang ingin Anda akses, termasuk jalur sumber daya dan parameter permintaan.

ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai

RequestHeader

Ya

Header permintaan umum. Pada sebagian besar kasus, informasi seperti nomor versi API, endpoint, dan informasi autentikasi termasuk di dalamnya. Untuk informasi lebih lanjut, lihat bagian "Request headers" dari topik ini.

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

Ya

Parameter permintaan yang didefinisikan dalam badan permintaan. Anda bisa mendapatkan badan permintaan dalam metadata API. Untuk informasi lebih lanjut, lihat Metadata API.

HTTPMethod

Ya

Metode permintaan. Anda bisa mendapatkan metode permintaan dalam metadata API. Untuk informasi lebih lanjut, lihat Metadata API.

POST

RequestHeader

Tabel berikut menjelaskan informasi yang harus dimasukkan dalam header permintaan umum saat memanggil operasi API Alibaba Cloud.

Header

Tipe

Diperlukan

Deskripsi

Contoh

host

String

Ya

Endpoint dari API layanan Alibaba Cloud. Untuk informasi lebih lanjut, lihat bagian Sintaks Permintaan HTTP dari topik ini.

ecs.cn-shanghai.aliyuncs.com

x-acs-action

String

Ya

Operasi yang ingin Anda lakukan. Anda dapat mencari operasi API yang ingin Anda panggil di OpenAPI Portal.

RunInstances

x-acs-content-sha256

String

Ya

Nilai hash dari badan permintaan. Nilai hash dikodekan dalam Base16. Nilai parameter ini sama dengan nilai parameter HashedRequestPayload.

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

x-acs-date

String

Ya

Timestamp dari permintaan. Tentukan waktu dalam format standar ISO 8601 dalam format yyyy-MM-ddTHH:mm:ssZ. Waktu harus dalam UTC. Contoh: 2018-01-01T12:00:00Z. Timestamp harus dalam jangka waktu 15 menit sebelum permintaan dikirim.

2023-10-26T10:22:32Z

x-acs-signature-nonce

String

Ya

Bilangan acak dan unik yang digunakan untuk mencegah serangan replay. Anda harus menggunakan bilangan yang berbeda untuk permintaan yang berbeda. Header ini hanya berlaku untuk protokol HTTP.

3156853299f313e23d1673dc12e1703d

x-acs-version

String

Ya

Nomor versi API. Untuk informasi lebih lanjut tentang nomor versi API, lihat bagian Bagaimana cara memperoleh versi API untuk x-acs-version dalam topik ini.

2014-05-26

Authorization

String

Ya jika permintaan bukan anonim

Informasi autentikasi yang digunakan untuk memvalidasi permintaan. Format: Authorization: SignatureAlgorithm Credential=AccessKeyId,SignedHeaders=SignedHeaders,Signature=Signature.

SignatureAlgorithm: metode enkripsi dari string tanda tangan. Setel nilainya menjadi ACS3-HMAC-SHA256.

Credential: ID AccessKey yang diberikan kepada Anda oleh Alibaba Cloud. Anda dapat melihat ID AccessKey Anda di Konsol Resource Access Management (RAM). Untuk informasi lebih lanjut tentang cara membuat pasangan AccessKey, lihat Dapatkan Pasangan AccessKey.

SignedHeaders: nama header permintaan yang digunakan untuk perhitungan tanda tangan. Kami sarankan Anda menggunakan semua header permintaan umum kecuali Authorization untuk perhitungan tanda tangan guna meningkatkan keamanan.

Signature: string tanda tangan dari permintaan saat ini. Untuk informasi lebih lanjut, lihat bagian "Metode Tanda Tangan" dari topik ini.

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

Ya jika Security Token Service (STS) digunakan untuk autentikasi

Token STS. Setel header ini ke nilai parameter SecurityToken dalam respons operasi AssumeRole.

Metode Tanda Tangan

API Gateway menggunakan ID AccessKey dan Rahasia AccessKey untuk menandatangani dan mengautentikasi permintaan. Untuk memastikan integritas dan keamanan data, API Gateway menghitung tanda tangan untuk setiap permintaan HTTP atau HTTPS dan membandingkannya dengan tanda tangan yang dibawa dalam permintaan untuk mengautentikasi identitas pemanggil.

Penting

Semua permintaan dan respons dikodekan dalam UTF-8.

Langkah 1: Buat permintaan kanonisasi

Buat permintaan kanonisasi berdasarkan pseudocode berikut:

CanonicalRequest =
  HTTPRequestMethod + '\n' +    // Metode permintaan HTTP dalam huruf kapital.
  CanonicalURI + '\n' +         // Uniform resource identifier (URI) yang dikanonisasi.
  CanonicalQueryString + '\n' + // String kueri yang dikanonisasi.
  CanonicalHeaders + '\n' +     // Header permintaan yang dikanonisasi.
  SignedHeaders + '\n' +        // Header permintaan yang digunakan untuk perhitungan tanda tangan.
  HashedRequestPayload // Nilai hash dari badan permintaan.

Metode Permintaan (HTTPRequestMethod)

Metode permintaan HTTP dalam huruf besar, seperti GET atau POST.

URI Kanonisasi (CanonicalURI)

URI yang telah dikanonisasi adalah jalur sumber daya yang telah dienkripsi dalam URL. Jalur sumber daya adalah bagian antara endpoint dan string query. Jalur tersebut mencakup garis miring maju (/) yang mengikuti endpoint tetapi tidak termasuk tanda tanya (?) yang mendahului string query. Anda harus menggunakan URI kanonisasi untuk perhitungan tanda tangan. Untuk membuat URI kanonisasi, enkode string yang dipisahkan oleh garis miring maju (/) dalam UTF-8 berdasarkan RFC 3986. Aturan enkoding:

  • Huruf, digit, tanda hubung (-), garis bawah (_), titik (.), dan tilde (~) tidak perlu dienkripsi.

    Karakter lain harus dienkripsi persen dalam format berikut: % + kode ASCII karakter dalam notasi heksadesimal. Sebagai contoh, tanda kutip ganda (") dienkripsi sebagai %22. Tabel berikut menjelaskan beberapa karakter khusus sebelum dan sesudah enkoding. Perhatikan karakter khusus ini.

    Sebelum Enkoding

    Setelah pengkodean

    Karakter spasi ( )

    %20

    Asterisk (*)

    %2A

    %7E

    Judul (~)

Jika Anda menggunakan java.net.URLEncoder dalam pustaka standar Java, kodekan string berdasarkan pustaka tersebut. Dalam string yang dikodekan, ganti tanda plus (+) dengan %20, tanda bintang (*) dengan %2A, dan %7E dengan tilde (~). Dengan cara ini, Anda akan memperoleh string yang dikodekan sesuai dengan aturan pengkodean di atas.

Penting

Jika gaya API adalah RPC, gunakan garis miring maju (/) sebagai nilai parameter CanonicalURI.

Jika gaya API adalah ROA, enkode nilai parameter path dalam metadata operasi API dan gunakan nilai yang telah dienkripsi sebagai nilai parameter CanonicalURI. Contoh: /api/v1/clusters.

String Query yang Dikanonisasi (CanonicalQueryString)

Dalam Metadata API, jika parameter permintaan dari permintaan API berisi informasi posisi "in":"query", Anda perlu menggabungkan parameter permintaan berdasarkan metode berikut:

  1. Urutkan semua parameter permintaan berdasarkan nama parameter secara alfabetis.

  2. Enkode nama dan nilai parameter dalam UTF-8 berdasarkan RFC 3986. Aturan enkoding sama dengan yang digunakan untuk membangun URI kanonisasi.

  3. Gunakan tanda sama dengan (=) untuk menggabungkan nama dan nilai parameter yang telah dienkripsi. Untuk parameter tanpa nilai, gunakan string kosong sebagai nilai parameter.

  4. Gunakan ampersand (&) untuk menggabungkan parameter yang telah dienkripsi dalam urutan yang diperoleh pada langkah sebelumnya.

Penting

Contoh:

ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai

HashedRequestPayload

Gunakan fungsi hash untuk mengonversi badan permintaan dan kodekan nilai hash dalam Base16 guna menghasilkan HashedRequestPayload. Kemudian, ubah nilai x-acs-content-sha256 dalam header permintaan menjadi nilai HashedRequestPayload. Pseudokode:

HashedRequestPayload = HexEncode(Hash(RequestBody))
  • Dalam metadata API, jika parameter permintaan berisi informasi posisi "in": "body" atau "in": "formData", tentukan parameter dalam badan permintaan.

    Catatan

    Jika permintaan tidak memiliki badan, tetapkan nilai RequestBody menjadi string kosong.

    • Jika parameter permintaan berisi informasi posisi "in": "formData", gabungkan menjadi string dalam format berikut: key1=value1&key2=value2&key3=value3, dan tambahkan header Content-Type serta tetapkan nilainya ke application/x-www-form-urlencoded. Misalnya, jika parameter permintaan bertipe array atau objek, ubah nilainya menjadi pasangan kunci-nilai yang diindeks.

    • Jika parameter permintaan berisi informasi posisi "in": "body", tambahkan header Content-Type ke permintaan. Nilai header Content-Type menentukan jenis konten permintaan. Contoh:

      • Jika konten permintaan adalah string JSON, tetapkan nilai header Content-Type ke application/json.

      • Jika konten permintaan adalah aliran file biner, tetapkan nilai header Content-Type ke application/octet-stream.

  • Hash() menentukan fungsi hash. Hanya algoritma SHA-256 yang didukung.

  • HexEncode() mengkodekan nilai hash dalam Base16. Fungsi ini mengembalikan nilai hash yang dikodekan dalam format heksadesimal dengan huruf kecil.

Contoh nilai saat badan permintaan kosong:

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Header yang Dikanonisasi (CanonicalHeaders)

Gabungkan parameter dalam header permintaan berdasarkan metode berikut:

  1. Filter header permintaan yang dimulai dengan x-acs-, header Host, dan header Content-Type.

  2. Ubah nama header menjadi huruf kecil dan urutkan header secara alfabetis.

  3. Hapus spasi sebelum dan sesudah nilai setiap header.

  4. Gabungkan nama header dan nilai header menggunakan titik dua (:), dan tambahkan baris baru (\n) di akhir pasangan nama-nilai untuk membentuk entri header kanonisasi.

  5. Gabungkan beberapa entri header kanonisasi menjadi satu string.

Catatan

Semua header permintaan kecuali header Authorization harus digunakan untuk perhitungan tanda tangan.

Pseudocode:

CanonicalHeaderEntry = Lowercase(HeaderName) + ':' + Trim(HeaderValue) + '\n'

CanonicalHeaders = 
    CanonicalHeaderEntry0 + CanonicalHeaderEntry1 + ... + CanonicalHeaderEntryN

Contoh:

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

Header yang Ditandatangani (SignedHeaders)

Header yang ditandatangani memberikan informasi tentang header permintaan umum yang digunakan untuk perhitungan tanda tangan. Nama setiap header yang ditandatangani sesuai dengan nama header kanonisasi. Anda dapat melakukan langkah-langkah berikut untuk membangun header yang ditandatangani:

  • Ubah nama header kanonisasi menjadi huruf kecil.

  • Urutkan header secara alfabetis dan gunakan titik koma (;) untuk memisahkan header.

Pseudocode:

SignedHeaders = Lowercase(HeaderName0) + ';' + Lowercase(HeaderName1) + ... + Lowercase(HeaderNameN) 

Contoh:

host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version

Langkah 2: Buat string-to-sign

Buat string-to-sign berdasarkan pseudocode berikut:

StringToSign =
    SignatureAlgorithm + '\n' +
    HashedCanonicalRequest
  • SignatureAlgorithm

    Hanya algoritma ACS3-HMAC-SHA256 yang didukung untuk perhitungan tanda tangan.

  • HashedCanonicalRequest

    Nilai hash dari permintaan yang telah dikanonisasi. Pseudocode berikut menunjukkan cara menghasilkan nilai hash:

    HashedCanonicalRequest = HexEncode(Hash(CanonicalRequest))
    • Hash() menentukan fungsi hash. Hanya algoritma SHA-256 yang didukung.

    • HexEncode() mengkodekan nilai hash dalam Base16. Fungsi ini mengembalikan nilai hash yang dikodekan dalam format heksadesimal dengan huruf kecil.

Contoh:

ACS3-HMAC-SHA256
7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259

Langkah 3: Hitung string tanda tangan

Hitung string tanda tangan berdasarkan pseudocode berikut:

Signature = HexEncode(SignatureMethod(Secret, StringToSign))
  • StringToSign: string-to-sign yang dibangun pada Langkah 2. Nilainya dikodekan dalam UTF-8.

  • SignatureMethod: Tentukan HMAC-SHA256 sebagai algoritma tanda tangan.

  • Secret: Rahasia AccessKey.

  • HexEncode: Fungsi yang digunakan untuk mengkodekan nilai dalam Base16.

Contoh:

06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0

Langkah 4: Tambahkan string tanda tangan ke permintaan

Setelah Anda mendapatkan string tanda tangan, tentukan header Authorization dalam permintaan dalam format berikut: Authorization:<SignatureAlgorithm> Credential=<AccessKeyId>,SignedHeaders=<SignedHeaders>,Signature=<Signature>.

Contoh:

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

Contoh Tanda Tangan

Untuk membantu Anda memahami metode tanda tangan sebelumnya, bagian ini menyediakan kode sampel berikut untuk sepenuhnya mengimplementasikan metode tanda tangan dalam bahasa pemrograman utama. Kode sampel berikut hanya disediakan untuk membantu Anda memahami metode tanda tangan dan tidak berlaku secara global. Alibaba Cloud menyediakan SDK untuk berbagai bahasa pemrograman dan kerangka pengembangan. Kami sarankan Anda menggunakan SDK Alibaba Cloud untuk memulai permintaan API dan secara otomatis menghasilkan tanda tangan untuk permintaan tersebut. Anda dapat mengembangkan aplikasi di Alibaba Cloud tanpa perlu menghitung tanda tangan secara manual.

Penting

Sebelum Anda menandatangani permintaan, baca dan pahami Metadata API, metode permintaan API, nama parameter permintaan, jenis parameter permintaan, dan cara parameter dilewatkan. Jika tidak, Anda mungkin gagal menandatangani permintaan.

Nilai Parameter Tetap

Dalam contoh ini, nilai sampel digunakan untuk menunjukkan bagaimana hasil dihasilkan di setiap langkah. Anda dapat menggunakan nilai sampel untuk mensimulasikan perhitungan, dan membandingkan hasil Anda dengan hasil dari contoh ini untuk menguji proses penandatanganan tanda tangan.

Parameter

Contoh

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

Parameter operasi spesifik

ImageId

win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd

RegionId

cn-shanghai

Anda dapat melakukan langkah-langkah berikut untuk menandatangani permintaan:

  1. Buat permintaan kanonisasi.

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
  1. Buat string-to-sign.

ACS3-HMAC-SHA256
7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259
  1. Hitung string tanda tangan.

06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0
  1. Tambahkan string tanda tangan ke permintaan.

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/json

Java

Catatan

Dalam contoh ini, lingkungan runtime JDK 1.8 digunakan. Sesuaikan parameter berdasarkan kebutuhan bisnis Anda.

Untuk menggunakan metode tanda tangan di Java, Anda harus menambahkan dependensi Maven berikut ke file pom.xml:

<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;
        // The request path.
        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<>();
        // The byte array of the parameters in the body.
        private byte[] body;
        // The query string. 
        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";

        /**
         * Calculate and generate a signature string
         */
        public static void getAuthorization(SignatureRequest signatureRequest,
                                            String accessKeyId, String accessKeySecret, String securityToken) {
            try {
                // Flatten the query parameters of the List and Map types.
                Map<String, Object> processedQueryParams = new TreeMap<>();
                processObject(processedQueryParams, "", signatureRequest.getQueryParam());
                signatureRequest.getQueryParam().clear();
                signatureRequest.getQueryParam().putAll(processedQueryParams);

                // Step 1: Construct a canonicalized request.
                String canonicalQueryString = buildCanonicalQueryString(signatureRequest.getQueryParam());

                // Calculate the hash value of the request body.
                String hashedRequestPayload = calculatePayloadHash(signatureRequest.getBody());
                signatureRequest.setHeaders("x-acs-content-sha256", hashedRequestPayload);

                // Add the security token if it exists.
                if (securityToken != null && !securityToken.isEmpty()) {
                    signatureRequest.setHeaders("x-acs-security-token", securityToken);
                }

                // Build the canonical headers and signed headers.
                CanonicalHeadersResult canonicalHeadersResult = buildCanonicalHeaders(signatureRequest.getHeaders());

                // Build the canonical request.
                String canonicalRequest = String.join("\n",
                        signatureRequest.getHttpMethod(),
                        signatureRequest.getCanonicalUri(),
                        canonicalQueryString,
                        canonicalHeadersResult.canonicalHeaders,
                        canonicalHeadersResult.signedHeaders,
                        hashedRequestPayload);

                System.out.println("canonicalRequest=========>\n" + canonicalRequest);

                // Step 2: Construct a string-to-sign.
                String hashedCanonicalRequest = sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8));
                String stringToSign = ALGORITHM + "\n" + hashedCanonicalRequest;
                System.out.println("stringToSign=========>\n" + stringToSign);

                // Step 3: Calculate the signature string.
                String signature = DatatypeConverter.printHexBinary(
                                hmac256(accessKeySecret.getBytes(StandardCharsets.UTF_8), stringToSign))
                        .toLowerCase();
                System.out.println("signature=========>" + signature);

                // Step 4: Specify the Authorization header.
                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);
            }
        }

        /**
         * Handle parameters of the formData type. 
         */
        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();
        }

        /**
         * Construct a canonicalized query string.
         */
        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("&"));
        }

        /**
         * Calculate the hash value of the request body.
         */
        private static String calculatePayloadHash(byte[] body) throws Exception {
            if (body != null) {
                return sha256Hex(body);
            } else {
                return sha256Hex("".getBytes(StandardCharsets.UTF_8));
            }
        }

        /**
         * Build canonical headers.
         */
        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); // Remove the trailing semicolon.
            }

            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;
            }
        }

        /**
         * Process complex object parameters.
         */
        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));
                }
            }
        }

        /**
         * Perform calculation by using the HMAC-SHA256 algorithm.
         */
        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));
        }

        /**
         * Calculate the hash value by using the SHA-256 algorithm.
         */
        private static String sha256Hex(byte[] input) throws Exception {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] digest = md.digest(input);
            return DatatypeConverter.printHexBinary(digest).toLowerCase();
        }

        /**
         * Perform URL encoding.
         */
        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);
            }
        }
    }

    /**
     * A sample signature. You need to adjust the parameters in the main() method. 
     * API operations in the ROA and API operations in the RPC style differ only in the logic of the canonicalUri value. 
     * <p>
     * Obtain the request method (methods), request parameter names (name), request parameter types (type), and request parameter positions (in) and encapsulate the information in the signature request (SignatureRequest). 
     *1. If the request parameters in the API metadata contain the "in":"query" position information, specify the parameters in the query string (queryParam). Note: You can also specify this type of parameter in the body and set content-type to application/x-www-form-urlencoded. For more information, see Example 3. 
     *2. If the request parameters in the API metadata contain the "in": "body" position information, specify the parameters in the body and set MIME to application/octet-stream or application/json. Note: For RPC APIs, application/json is not recommended; use the method in Example 3 instead. 
     3. If the request parameters in the API metadata contain the "in": "formData" position information, specify the parameters in the body and set MIME to application/x-www-form-urlencoded. 
     */
    public static void main(String[] args) throws IOException {
        // Obtain the AccessKey ID from the environment variable.
        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("Please set the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables.");
            return;
        }

        // Example 1: Call the DescribeInstanceStatus operation of Elastic Compute Service (ECS) in the RPC style (Parameter position: "in":"query"). 
        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"));

        /*// Example 2: Call the RecognizeGeneral operation in the RPC style of OCR (Parameter position: "in":"body"). 
        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");*/

        /*// Example 3: Call the TranslateGeneral operation of Machine Translation in the RPC style (Parameter position: "in": "formData" or "in":"body"). 
        String httpMethod = "POST";
        String canonicalUri = "/";
        String host = "mt.aliyuncs.com";
        String xAcsAction = "TranslateGeneral";
        String xAcsVersion = "2018-10-12";
        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
        Map<String, Object> body = new HashMap<>();
        body.put("FormatType", "text");
        body.put("SourceLanguage", "zh");
        body.put("TargetLanguage", "en");
        body.put("SourceText", "Hello");
        body.put("Scene", "general");
        String formDataToString = SignatureService.formDataToString(body);
        signatureRequest.setBody(formDataToString.getBytes(StandardCharsets.UTF_8));
        signatureRequest.setHeaders("content-type", "application/x-www-form-urlencoded");*/

        /*// Construct a POST request for the CreateCluster operation of Container Service for Kubernetes (ACK) in the ROA style. 
        SignatureRequest signatureRequest = new SignatureRequest(
                "POST",
                "/clusters",
                "cs.cn-chengdu.aliyuncs.com",
                "CreateCluster",
                "2015-12-15");
        TreeMap<String, Object> body = new TreeMap<>();
        body.put("name", "Test");
        body.put("cluster_type", "ManagedKubernetes");
        body.put("kubernetes_version", "1.34.1-aliyun.1");
        body.put("region_id", "cn-chengdu");
        body.put("snat_entry", true);
        body.put("deletion_protection", true);
        body.put("proxy_mode", "ipvs");
        body.put("profile", "Default");
        body.put("timezone", "Asia/Shanghai");
        body.put("cluster_spec", "ack.pro.small");
        body.put("enable_rrsa", false);
        body.put("service_cidr", "192.168.0.0/16");
        body.put("zone_ids", Arrays.asList("cn-chengdu-b","cn-chengdu-b"));
        Gson gson = (new GsonBuilder()).disableHtmlEscaping().create();
        signatureRequest.setBody(gson.toJson(body).getBytes(StandardCharsets.UTF_8));
        signatureRequest.setHeaders("content-type", "application/json");*/

        /*// Construct a GET request for the DescribeClusterResources operation of ACK in the ROA style. 
        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);*/

        /*// Construct a DELETE request for the DeleteCluster operation of ACK in the ROA style. 
        SignatureRequest signatureRequest = new SignatureRequest(
                "DELETE",
                "/clusters/" + SignatureService.percentCode("c299f90b63b************"),
                "cs.cn-chengdu.aliyuncs.com",
                "DeleteCluster",
                "2015-12-15");*/

        // Generate a signature.
        SignatureService.getAuthorization(signatureRequest, accessKeyId, accessKeySecret, securityToken);

        // Test if the API can be called successfully
        callApi(signatureRequest);
    }

    /**
     * For testing only
     */
    private static void callApi(SignatureRequest signatureRequest) {
        try {
            String url = "https://" + signatureRequest.getHost() + signatureRequest.getCanonicalUri();
            URIBuilder uriBuilder = new URIBuilder(url);

            // Specify the query parameters.
            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");
            }

            // Add request headers.
            for (Map.Entry<String, String> entry : signatureRequest.getHeaders().entrySet()) {
                httpRequest.addHeader(entry.getKey(), entry.getValue());
            }

            // Send a request.
            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

Catatan

Dalam contoh ini, lingkungan runtime Python 3.12.3 digunakan. Sesuaikan parameter berdasarkan kebutuhan bisnis Anda.

Anda harus menginstal pustaka pytz dan requests secara manual. Jalankan perintah berikut di terminal berdasarkan versi Python Anda:

Python3

pip3 install pytz
pip3 install requests
import 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:
        """Sort query parameters by parameter name in alphabetical order and return the encoded string."""
        self.query_param = dict(sorted(self.query_param.items()))

    def sorted_headers(self) -> None:
        """Sort request headers by header name in alphabetical order and return the encoded string."""
        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()

        # Step 1: Construct a canonicalized request.
        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)

        # Step 2: Construct a string-to-sign.
        hashed_canonical_request = sha256_hex(canonical_request.encode("utf-8"))
        string_to_sign = f"{ALGORITHM}\n{hashed_canonical_request}"
        print(string_to_sign)

        # Step 3: Calculate the signature string.
        signature = hmac256(ACCESS_KEY_SECRET.encode("utf-8"), string_to_sign).hex().lower()

        # Step 4: Specify the Authorization header.
        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", "~")


# Obtain the AccessKey ID and AccessKey secret from environment variables.
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"

"""
This is a sample signature. During debugging, you can modify the value of the main function as needed. For example, if you want to call the SendSms operation, you can refer to Example 1 and modify the http_method, host, x_acs_action, x_acs_version, and query_param parameter values. 
Between ROA-style API operations and RPC-style API operations, only the canonicalUri parameter follows different value assignment logic. 

Obtain the request method (methods), request parameter names (name), request parameter types (type), and request parameter positions (in) and encapsulate the information in the signature request (SignatureRequest). 
1. If the request parameters in the API metadata contain the "in":"query" position information, specify the parameters in the query string (queryParam). You do not need to specify the content-type parameter. Note: You can also specify this type of parameter in the body and set content-type to application/x-www-form-urlencoded. For more information, see Example 3. 
2. If the request parameters in the API metadata contain the "in": "body" position information, specify the parameters in the body. Specify a value for content-type based on the actual scenario. Note: For RPC APIs, application/json is not recommended; use the method in Example 3 instead. 
3. If the request parameters in the API metadata contain the "in": "formData" position information, specify the parameters in the body and set content-type to application/x-www-form-urlencoded. 
"""
if __name__ == "__main__":
    # Example 1: Call an API operation in the RPC style (Parameter position: "in":"query")
    http_method = "POST"  # The request method, which can be obtained from the API metadata. We recommend that you use the POST method. 
    canonical_uri = "/"  # The resource path of an RPC-style API operation is empty. Therefore, a forward slash (/) is used as the value of the CanonicalURI parameter.
    host = "ecs.cn-hangzhou.aliyuncs.com"  # The endpoint of the cloud service.
    x_acs_action = "DescribeInstanceStatus"  # The API operation that you want to perform.
    x_acs_version = "2014-05-26"  # The version number of the API.
    signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
    # Request parameters for calling the DescribeInstanceStatus operation:
    # RegionId is defined as a string in the API metadata. It is a required parameter and the position is "in":"query".
    signature_request.query_param['RegionId'] = 'cn-hangzhou'
    # InstanceId is defined as a string in the API metadata. It is an optional parameter and the position is "in":"query".
    signature_request.query_param['InstanceId'] = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX",
                                                   "i-bp1incuofvzxXXXXXXXX"]

    # # Example 2: Call an API operation in the RPC style (Parameter position: "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)
    # # If the request parameters in the API metadata contain the "in": "body" position information, specify the parameters in the body. 
    # file_path = "D:\\test.png"
    # with open(file_path, 'rb') as file:
    #     # Read the image content as a byte array.
    #     signature_request.body = file.read()
    #     signature_request.headers["content-type"] = "application/octet-stream"

    // // Example 3: Call an API operation in the RPC style (Parameter position: "in": "formData" or "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)
    # # Request parameters for calling the TranslateGeneral operation:
    # # Context is defined as a string in the API metadata. It is an optional parameter and the position is "in":"query".
    # signature_request.query_param['Context'] = 'Morning'
    # # The position of the FormatType, SourceLanguage, and TargetLanguage parameters in the metadata is defined as "in":"formData".
    # form_data = OrderedDict()
    # form_data["FormatType"] = "text"
    # form_data["SourceLanguage"] = "zh"
    # form_data["TargetLanguage"] = "en"
    # form_data["SourceText"] = "Hello"
    # form_data["Scene"] = "general"
    # signature_request.body = bytes(form_data_to_string(form_data), 'utf-8')
    # signature_request.headers["content-type"] = "application/x-www-form-urlencoded"

    # # Example 4: Send a POST request to call an ROA-style API operation
    # 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)
    # If the request parameters in the API metadata contain the "in": "body" position information, specify the parameters in the 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"

    # # Example 5: Send a GET request to call an ROA-style API operation
    # http_method = "GET"
    # # If the value of the canonicalUri parameter contains the value of the path parameter, encode the value of the path parameter by specifying percent_code({Value of the path parameter}).
    # cluster_id_encode = percent_code("ca72cfced86db497cab79aa28XXXXXXXX")
    # canonical_uri = f"/clusters/{cluster_id_encode}/resources"
    # host = "cs.cn-beijing.aliyuncs.com"
    # x_acs_action = "DescribeClusterResources"
    # x_acs_version = "2015-12-15"
    # signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
    # signature_request.query_param['with_addon_resources'] = True

    # # Example 6: Send a DELETE request to call an ROA-style API operation
    # http_method = "DELETE"
    # # If the value of the canonicalUri parameter contains the value of the path parameter, encode the value of the path parameter by specifying percent_code({Value of the path parameter}).
    # cluster_id_encode = percent_code("ca72cfced86db497cab79aa28XXXXXXXX")
    # canonical_uri = f"/clusters/{cluster_id_encode}"
    # host = "cs.cn-beijing.aliyuncs.com"
    # x_acs_action = "DeleteCluster"
    # x_acs_version = "2015-12-15"
    # signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)

    get_authorization(signature_request)
    call_api(signature_request)

Go

Catatan

Dalam contoh ini, lingkungan runtime go1.22.2 digunakan. Sesuaikan parameter berdasarkan kebutuhan bisnis Anda.

Jalankan perintah berikut di terminal:

go get github.com/google/uuid
go get golang.org/x/exp/maps
package 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() specifies that an AccessKey ID and an AccessKey secret are obtained from environment variables. 
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"
)

// A sample signature. You need to adjust the parameters in the main() method. 
// API operations in the ROA and API operations in the RPC style differ only in the logic of the canonicalUri value. 
// Obtain the request method (methods), request parameter names (name), request parameter types (type), and request parameter positions (in) and encapsulate the information in the signature request (SignatureRequest). 
// 1. If the request parameters in the API metadata contain the "in":"query" position information, specify the parameters in the query string (queryParam). Note: You can also specify this type of parameter in the body and set content-type to application/x-www-form-urlencoded. For more information, see Example 3. 
*2. If the request parameters in the API metadata contain the "in": "body" position information, specify the parameters in the body and set MIME to application/octet-stream or application/json. Note: For RPC APIs, application/json is not recommended; use the method in Example 3 instead. 
3. If the request parameters in the API metadata contain the "in": "formData" position information, specify the parameters in the body and set MIME to application/x-www-form-urlencoded. 
func main() {
	// Example 1: Call an API operation in the RPC style (Parameter position: "in":"query")
	httpMethod := "POST"                   // The HTTP request method. In most cases, you can use the POST or GET method to call an API operation in the RPC style. In this example, POST is used.
	canonicalUri := "/"                    // The resource path of an RPC API operation is empty. Therefore, a forward slash (/) is used as the value of the CanonicalURI parameter.
	host := "ecs.cn-hangzhou.aliyuncs.com" // The endpoint of the cloud service.
	xAcsAction := "DescribeInstanceStatus" // The API operation that you want to perform.
	xAcsVersion := "2014-05-26"            // The version number of the API.
	req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
	// Request parameters for calling the DescribeInstanceStatus operation:
	// RegionId is defined as a string in the API metadata. It is a required parameter and the position is "in":"query".
	req.queryParam["RegionId"] = "cn-hangzhou"
	// InstanceId is defined as a string in the API metadata. It is an optional parameter and the position is "in":"query".
	instanceIds := []interface{}{"i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"}
	req.queryParam["InstanceId"] = instanceIds

	// // Example 2: Call an API operation in the RPC style (Parameter position: "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)
	// Read the image file content.
	// 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"

	// // Example 3: Call an API operation in the RPC style (Parameter position: "in": "formData" or "in":"body")
	// httpMethod := "POST"
	// canonicalUri := "/"
	// host := "mt.aliyuncs.com"
	// xAcsAction := "TranslateGeneral"
	// xAcsVersion := "2018-10-12"
	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
	// // Request parameters for calling the TranslateGeneral operation:
	// // Context is defined as a string in the API metadata. It is an optional parameter and the position is "in":"query".
	// req.queryParam["Context"] = "Morning"
	// // The position of the FormatType, SourceLanguage, and TargetLanguage parameters in the metadata is defined as "in":"formData".
	// body := make(map[string]interface{})
	// body["FormatType"] = "text"
	// body["SourceLanguage"] = "zh"
	// body["TargetLanguage"] = "en"
	// body["SourceText"] = "Hello"
	// body["Scene"] = "general"
	// str := formDataToString(body)
	// req.body = []byte(*str)
	// req.headers["content-type"] = "application/x-www-form-urlencoded"

	// // Construct a POST request for an API operation in the ROA style.
	// httpMethod := "POST"
	// canonicalUri := "/clusters"
	// host := "cs.cn-beijing.aliyuncs.com"
	// xAcsAction := "CreateCluster"
	// xAcsVersion := "2015-12-15"
	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
	// // Encapsulate the request parameters. If the request parameters in the metadata contain the "in": "body" position information, specify the parameters in the 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"

	// // Construct a GET request for a ROA API operation.
	// httpMethod := "GET"
	// // If the value of the canonicalUri parameter contains the value of the path parameter, encode the value of the path parameter by specifying percentCode({Value of the path parameter}).
	// canonicalUri := "/clusters/" + percentCode("c558c166928f9446dae400d106e124f66") + "/resources"
	// host := "cs.cn-beijing.aliyuncs.com"
	// xAcsAction := "DescribeClusterResources"
	// xAcsVersion := "2015-12-15"
	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
	// req.queryParam["with_addon_resources"] = "true"

	// // Construct a DELETE request for a ROA API operation.
	// httpMethod := "DELETE"
	// // If the value of the canonicalUri parameter contains the value of the path parameter, encode the value of the path parameter by specifying percentCode({Value of the path parameter}).
	// canonicalUri := "/clusters/" + percentCode("c558c166928f9446dae400d106e124f66")
	// host := "cs.cn-beijing.aliyuncs.com"
	// xAcsAction := "DeleteCluster"
	// xAcsVersion := "2015-12-15"
	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)

	// Sign the request.
	getAuthorization(req)
	// Call the API operation.
	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) {
	// Flatten the query parameters of the List and Map types.
	newQueryParams := make(map[string]interface{})
	processObject(newQueryParams, "", req.queryParam)
	req.queryParam = newQueryParams
	// Step 1: Construct a canonicalized request.
	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)

	// Step 2: Construct a string-to-sign.
	hashedCanonicalRequest := sha256Hex([]byte(canonicalRequest))
	stringToSign := ALGORITHM + "\n" + hashedCanonicalRequest
	fmt.Printf("stringToSign========>\n%s\n", stringToSign)

	// Step 3: Calculate the signature string.
	byteData, err := hmac256([]byte(AccessKeySecret), stringToSign)
	if err != nil {
		fmt.Println(err)
		panic(err)
	}
	signature := strings.ToLower(hex.EncodeToString(byteData))

	// Step 4: Specify the Authorization header.
	authorization := ALGORITHM + " Credential=" + AccessKeyId + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature
	req.headers["Authorization"] = authorization
}

func hmac256(key []byte, toSignString string) ([]byte, error) {
	// Obtain the object on which the HMAC-SHA256 algorithm is implemented.
	h := hmac.New(sha256.New, key)
	// Construct a string-to-sign.
	_, err := h.Write([]byte(toSignString))
	if err != nil {
		return nil, err
	}
	// Calculate the hash value of the signature string and return the hash value.
	return h.Sum(nil), nil
}

func sha256Hex(byteArray []byte) string {
	// Obtain the object on which the SHA-256 algorithm is implemented.
	hash := sha256.New()
	// Write the signature string to the hash function.
	_, _ = hash.Write(byteArray)
	// Calculate the hash value of the signature string by using the SHA-256 algorithm and return the hash value as a string in the hexadecimal format in lowercase letters.
	hexString := hex.EncodeToString(hash.Sum(nil))

	return hexString
}

func percentCode(str string) string {
	// Replace specific characters.
	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: the objects on which recursion is performed. Complex objects such as maps and lists are recursively decomposed into key-value pairs.
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

Catatan

Dalam contoh ini, lingkungan runtime Node.js v20.13.1 digunakan. Sesuaikan parameter berdasarkan kebutuhan bisnis Anda.

Dalam contoh ini, Node.js digunakan.

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;
        // Step 1: Construct a canonicalized request.
        const canonicalQueryString = Object.entries(signRequest.queryParam)
            .sort(([a], [b]) => a.localeCompare(b))
            .map(([key, value]) => `${percentCode(key)}=${percentCode(value)}`)
            .join('&');

        // The request body. If the request body is empty, such as in a GET request, use an empty string as the value of the RequestPayload parameter.
        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;
        }

        // Convert all header names to lowercase.
        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();
        // The request headers that are used for signature calculation. Use semicolons (;) to concatenate all the headers by lowercase header name in alphabetical order.
        const signedHeaders = sortedKeys.join(";")
        // Construct request headers. Concatenate multiple canonicalized request headers by lowercase header name in alphabetical order.
        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);

        // Step 2: Construct a string-to-sign.
        const hashedCanonicalRequest = sha256Hex(encoder.encode(canonicalRequest));
        const stringToSign = `${ALGORITHM}\n${hashedCanonicalRequest}`;
        console.log('stringToSign=========>', stringToSign);

        // Step 3: Calculate the signature string.
        const signature = hmac256(accessKeySecret, stringToSign);
        console.log('signature=========>', signature);

        // Step 4: Specify the Authorization header.
        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}`;
        // Configure request parameters.
        if (signRequest.queryParam) {
            const query = new URLSearchParams(signRequest.queryParam);
            url += '?' + query.toString();
        }
        console.log('url=========>', url);

        // Configure request options.
        let options = {
            method: signRequest.httpMethod.toUpperCase(),
            headers: signRequest.headers
        };

        // Process the request body.
        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) {
    // No further processing is required for a null value.
    if (value === null) {
        return;
    }
    if (key === null) {
        key = "";
    }

    // If the value is of the Array type, traverse the array and perform recursion on each element.
    if (Array.isArray(value)) {
        value.forEach((item, index) => {
            processObject(map, `${key}.${index + 1}`, item);
        });
    } else if (typeof value === 'object' && value !== null) {
        // If the value is of the Object type, traverse the object and perform recursion on each key-value pair.
        Object.entries(value).forEach(([subKey, subValue]) => {
            processObject(map, `${key}.${subKey}`, subValue);
        });
    } else {
        // If a key starts with a period (.), remove the period (.) to maintain the continuity of keys.
        if (key.startsWith('.')) {
            key = key.slice(1);
        }
        map[key] = String(value);
    }
}

/**
 * A sample signature. You need to adjust the parameters in the main() method. 
 * API operations in the ROA and API operations in the RPC style differ only in the logic of the canonicalUri value. 
 *
 * Obtain the request method (methods), request parameter names (name), request parameter types (type), and request parameter positions (in) and encapsulate the information in the signature request (SignatureRequest). 
 *1. If the request parameters in the API metadata contain the "in":"query" position information, specify the parameters in the query string (queryParam). Note: You can also specify this type of parameter in the body and set content-type to application/x-www-form-urlencoded. For more information, see Example 3. 
 *2. If the request parameters in the API metadata contain the "in": "body" position information, specify the parameters in the body and set MIME to application/octet-stream or application/json. Note: For RPC APIs, application/json is not recommended; use the method in Example 3 instead. 
 3. If the request parameters in the API metadata contain the "in": "formData" position information, specify the parameters in the body and set MIME to application/x-www-form-urlencoded. 
 */

// Example 1: Call an API operation in the RPC style (Parameter position: "in":"query")
const httpMethod = 'POST'; // The HTTP request method. In most cases, you can use the POST or GET method to call an API operation in the RPC style. In this example, POST is used.
const canonicalUri = '/'; // The resource path of an RPC API operation is empty. Therefore, a forward slash (/) is used as the value of the CanonicalURI parameter.
const host = 'ecs.cn-hangzhou.aliyuncs.com'; // endpoint
const xAcsAction = 'DescribeInstanceStatus'; // The API operation that you want to perform.
const xAcsVersion = '2014-05-26'; // The version number of the API.
const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// Request parameters for calling the DescribeInstanceStatus operation:
signRequest.queryParam = {
    // RegionId is defined as a string in the API metadata. It is a required parameter and the position is "in":"query".
    RegionId: 'cn-hangzhou',
    // InstanceId is defined as a string in the API metadata. It is an optional parameter and the position is "in":"query".
    InstanceId: ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"],
}


// // Example 2: Call an API operation in the RPC style (Parameter position: "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);
// // If the request parameters in the API metadata contain the "in": "body" position information, specify the parameters in the body.
// signRequest.body = bytes;
// signRequest.headers['content-type'] = 'application/octet-stream';


// // Example 3: Call an API operation in the RPC style (Parameter position: "in": "formData" or "in":"body")
// const httpMethod = 'POST'; // The HTTP request method. In most cases, you can use the POST or GET method to call an API operation in the RPC style. In this example, POST is used.
// const canonicalUri = '/'; // The resource path of an RPC API operation is empty. Therefore, a forward slash (/) is used as the value of the CanonicalURI parameter.
// const host = 'mt.aliyuncs.com'; // endpoint
// const xAcsAction = 'TranslateGeneral'; // The API operation that you want to perform.
// const xAcsVersion = '2018-10-12'; // The version number of the API.
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// // Request parameters for calling the TranslateGeneral operation:
// // Context is defined as a string in the API metadata. It is an optional parameter and the position is "in":"query".
// signRequest.queryParam["Context"] = "Morning";
// // The position of the FormatType, SourceLanguage, and TargetLanguage parameters in the metadata is defined as "in":"formData".
// const formData = {
//     SourceLanguage: "zh",
//     TargetLanguage: "en",
//     FormatType: "text",
//     Scene: "general",
//     SourceText: 'Hello'
// }
// const str = formDataToString(formData)
// signRequest.body = encoder.encode(str);
// signRequest.headers['content-type'] = 'application/x-www-form-urlencoded';


// // Construct a POST request for a ROA API operation.
// 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);
// // If the request parameters in the API metadata contain the "in": "body" position information, specify the parameters in the 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';


// // Construct a GET request for a ROA API operation.
// const httpMethod = 'GET';
// // If the value of the canonicalUri parameter contains the value of the path parameter, encode the value of the path parameter by specifying percentCode({Value of the path parameter}).
// const canonicalUri = '/clusters/' + percentCode("c28c2615f8bfd466b9ef9a76c61706e96") + '/resources';
// const host = 'cs.cn-beijing.aliyuncs.com';
// const xAcsAction = 'DescribeClusterResources';
// const xAcsVersion = '2015-12-15';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// signRequest.queryParam = {
//     with_addon_resources: true,
// }


// // Construct a DELETE request for a ROA API operation.
// const httpMethod = 'DELETE';
// // If the value of the canonicalUri parameter contains the value of the path parameter, encode the value of the path parameter by specifying percentCode({Value of the path parameter}).
// const canonicalUri = '/clusters/' + percentCode("c28c2615f8bfd466b9ef9a76c61706e96");
// const host = 'cs.cn-beijing.aliyuncs.com';
// const xAcsAction = 'DeleteCluster';
// const xAcsVersion = '2015-12-15';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);

getAuthorization(signRequest);
// Call the API operation.
callApi(signRequest).then(r => {
    console.log(r);
}).catch(error => {
    console.error(error);
});

PHP

Catatan

Dalam contoh ini, lingkungan runtime PHP 7.4.33 digunakan. Sesuaikan parameter berdasarkan kebutuhan bisnis Anda.

<?php

class SignatureDemo
{
    // The encryption algorithm.
    private $ALGORITHM;
    // Access Key ID
    private $AccessKeyId;
    // Access Key Secret
    private $AccessKeySecret;

    private $SecurityToken;

    public function __construct()
    {
        date_default_timezone_set('UTC'); // Set the time zone to GMT.
        $this->AccessKeyId = getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'); // getenv() specifies that the AccessKey ID of the RAM user is obtained from the environment variables.
        $this->AccessKeySecret = getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'); // getenv() specifies that the AccessKey secret of the RAM user is obtained from the environment variables.
        $this->SecurityToken = getenv('ALIBABA_CLOUD_SECURITY_TOKEN');
        $this->ALGORITHM = 'ACS3-HMAC-SHA256'; // Specify the encryption method.
    }

    /**
     * A sample signature. You need to adjust the parameters in the main() method. 
     * API operations in the ROA and API operations in the RPC style differ only in the logic of the canonicalUri value. 
     *
     * Obtain the request method (methods), request parameter names (name), request parameter types (type), and request parameter positions (in) and encapsulate the information in the signature request (SignatureRequest). 
     *1. If the request parameters in the API metadata contain the "in":"query" position information, specify the parameters in the query string (queryParam). Note: You can also specify this type of parameter in the body and set content-type to application/x-www-form-urlencoded. For more information, see Example 3. 
     *2. If the request parameters in the API metadata contain the "in": "body" position information, specify the parameters in the body and set MIME to application/octet-stream or application/json. Note: For RPC APIs, application/json is not recommended; use the method in Example 3 instead. 
     3. If the request parameters in the API metadata contain the "in": "formData" position information, specify the parameters in the body and set MIME to application/x-www-form-urlencoded. 
     */
    public function main()
    {
        // Example 1: Call an API operation in the RPC style (Parameter position: "in":"query")
        $request = $this->createRequest('POST', '/', 'ecs.cn-hangzhou.aliyuncs.com', 'DescribeInstanceStatus', '2014-05-26');
        // Request parameters for calling the DescribeInstanceStatus operation:
        $request['queryParam'] = [
            // RegionId is defined as a string in the API metadata. It is a required parameter and the position is "in":"query".
            'RegionId' => 'cn-hangzhou',
            // InstanceId is defined as a string in the API metadata. It is an optional parameter and the position is "in":"query".
            'InstanceId' => ["i-bp11ht4h2kdXXXXXXXX", "i-bp16maz3h3xgXXXXXXXX", "i-bp10r67hmslXXXXXXXX"]
        ];

        // // Example 2: Call an API operation in the RPC style (Parameter position: "in":"body")
        // $request = $this->createRequest('POST', '/', 'ocr-api.cn-hangzhou.aliyuncs.com', 'RecognizeGeneral', '2021-07-07');
        // // If the request parameters in the API metadata contain the "in": "body" position information, specify the parameters in the body. 
        // $filePath = 'D:\\test.png';
        // // Specify a binary file.
        // $fileResource = fopen($filePath, 'rb');
        // $request['body'] = stream_get_contents($fileResource); 
        // $request['headers']['content-type'] = 'application/octet-stream'; // Set Content-Type to application/octet-stream.
        // // Close the file.
        // fclose($fileResource);


        // // Example 3: Call an API operation in the RPC style (Parameter position: "in": "formData" or "in":"body")
        // $request = $this->createRequest('POST', '/', 'mt.aliyuncs.com', 'TranslateGeneral', '2018-10-12');
        // // Request parameters for calling the TranslateGeneral operation:
        // $request['queryParam'] = [
        //     // Context is defined as a string in the API metadata. It is an optional parameter and the position is "in":"query".
        //     'Context' => 'Morning',
        // ];
        // $formData = [
        //     'FormatType' => 'text',
        //     'SourceLanguage' => 'zh',
        //     'TargetLanguage' => 'en',
        //     'SourceText' => 'Hello',
        //     'Scene' => 'general',
        // ];
        // $str = self::formDataToString($formData);
        // $request['body'] = $str;
        // $request['headers']['content-type'] = 'application/x-www-form-urlencoded';


        // // Construct a POST request for a ROA API operation.
        // $request = $this->createRequest('POST', '/clusters', 'cs.cn-beijing.aliyuncs.com', 'CreateCluster', '2015-12-15');
        // $bodyData = [
        //     'name' => 'Test cluster',
        //     'region_id' => 'cn-beijing',
        //     'cluster_type' => 'ExternalKubernetes',
        //     'vpcid' => 'vpc-2zeou1uod4ylaXXXXXXXX',
        //     'service_cidr' => '10.2.0.0/24',
        //     'security_group_id' => 'sg-2ze1a0rlgeo7XXXXXXXX',
        //     "vswitch_ids" => [
        //         "vsw-2zei30dhfldu8XXXXXXXX"
        //     ]
        // ];
        // $request['body'] = json_encode($bodyData, JSON_UNESCAPED_UNICODE);
        // $request['headers']['content-type'] = 'application/json; charset=utf-8'; 


        // // Construct a GET request for a ROA API operation.
        // // If the value of the canonicalUri parameter contains the value of the path parameter, encode the value of the path parameter by specifying rawurlencode({Value of the path parameter}).
        // $cluster_id = 'c930976b3b1fc4e02bc09831dXXXXXXXX';
        // $canonicalUri = sprintf("/clusters/%s/resources", rawurlencode($cluster_id));
        // $request = $this->createRequest('GET', $canonicalUri, 'cs.cn-beijing.aliyuncs.com', 'DescribeClusterResources', '2015-12-15');
        // $request['queryParam'] = [
        //     'with_addon_resources' => true,
        // ];


        // // Construct a DELETE request for a ROA API operation.
        // $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);
        // Call the API operation.
        $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 {
            // Send the request by using cURL.
            $url = "https://" . $request['host'] . $request['canonicalUri'];

            // Add the request parameters to the URL.
            if (!empty($request['queryParam'])) {
                $url .= '?' . http_build_query($request['queryParam']);
            }

            echo $url;
            // Initialize a cURL session.
            $ch = curl_init();

            // Set cURL options.
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL certificate verification. This increases security risks. We recommend that you do not disable SSL certificate verification in production environments. ! !)
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return the results but do not output the results.
            curl_setopt($ch, CURLOPT_HTTPHEADER, $this->convertHeadersToArray($request['headers'])); // Add request headers.

            // Set cURL options based on the request type.
            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");
            }

            // Send a request.
            $result = curl_exec($ch);

            // Check whether an error occurs.
            if (curl_errno($ch)) {
                echo "Failed to send request: " . curl_error($ch);
            } else {
                echo $result;
            }

        } catch (Exception $e) {
            echo "Error: " . $e->getMessage();
        } finally {
            // Close the cURL session.
            curl_close($ch);
        }
    }

    function formDataToString($formData)
    {
        $res = self::processObject($formData);
        return http_build_query($res);
    }

    function processObject($value)
    {
        // No further processing is required for a null 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

Catatan

Dalam contoh ini, lingkungan runtime .NET 8.0.302 digunakan. Sesuaikan parameter berdasarkan kebutuhan bisnis Anda.

using System.Globalization;
using System.Net;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using Newtonsoft.Json;

namespace SignatureDemo
{
    public class Request
    {
        public string HttpMethod { get; private set; }
        public string CanonicalUri { get; private set; }
        public string Host { get; private set; }
        public string XAcsAction { get; private set; }
        public string XAcsVersion { get; private set; }
        public SortedDictionary<string, object> Headers { get; private set; }
        public byte[]?  Body { get; set; }
        public Dictionary<string, object> QueryParam { get; set; }

        public Request(string httpMethod, string canonicalUri, string host, string xAcsAction, string xAcsVersion)
        {
            HttpMethod = httpMethod;
            CanonicalUri = canonicalUri;
            Host = host;
            XAcsAction = xAcsAction;
            XAcsVersion = xAcsVersion;
            Headers = [];
            QueryParam = [];
            Body = null;
            InitHeader();
        }

        private void InitHeader()
        {
            Headers["host"] = Host;
            Headers["x-acs-action"] = XAcsAction;
            Headers["x-acs-version"] = XAcsVersion;
            DateTime utcNow = DateTime.UtcNow;
            Headers["x-acs-date"] = utcNow.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture);
            Headers["x-acs-signature-nonce"] = Guid.NewGuid().ToString();
        }
    }

    public class Program
    {
        private static readonly string AccessKeyId = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID") ??  throw new InvalidOperationException("The environment variable ALIBABA_CLOUD_ACCESS_KEY_ID is not specified.");
        private static readonly string AccessKeySecret = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET") ??  throw new InvalidOperationException("The environment variable ALIBABA_CLOUD_ACCESS_KEY_SECRET is not specified.");
        private static readonly string?  SecurityToken = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_SECURITY_TOKEN");
        private const string Algorithm = "ACS3-HMAC-SHA256";
        private const string ContentType = "content-type";

        /**
        * A sample signature. You need to adjust the parameters in the main() method. 
        * API operations in the ROA and API operations in the RPC style differ only in the logic of the canonicalUri value. 
        *
        * Obtain the request method (methods), request parameter names (name), request parameter types (type), and request parameter positions (in) and encapsulate the information in the signature request (SignatureRequest). 
        *1. If the request parameters in the API metadata contain the "in":"query" position information, specify the parameters in the query string (queryParam). Note: You can also specify this type of parameter in the body and set content-type to application/x-www-form-urlencoded. For more information, see Example 3. 
        *2. If the request parameters in the API metadata contain the "in": "body" position information, specify the parameters in the body and set MIME to application/octet-stream or application/json. Note: For RPC APIs, application/json is not recommended; use the method in Example 3 instead. 
        3. If the request parameters in the API metadata contain the "in": "formData" position information, specify the parameters in the body and set MIME to application/x-www-form-urlencoded. 
        */
        public static void Main(string[] args)
        {
            // Example 1: Call an API operation in the RPC style (Parameter position: "in":"query")
            string httpMethod = "POST"; // The HTTP request method. In most cases, you can use the POST or GET method to call an API operation in the RPC style. In this example, POST is used.
            string canonicalUri = "/"; // The resource path of an RPC API operation is empty. Therefore, a forward slash (/) is used as the value of the CanonicalURI parameter.
            string host = "ecs.cn-hangzhou.aliyuncs.com"; // The endpoint of the cloud service.
            string xAcsAction = "DescribeInstanceStatus"; // The API operation that you want to perform.
            string xAcsVersion = "2014-05-26"; // The version number of the API.
            var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
            // Request parameters for calling the DescribeInstanceStatus operation:
            // RegionId is defined as a string in the API metadata. It is a required parameter and the position is "in":"query".
            request.QueryParam["RegionId"] = "cn-hangzhou"; 
            // InstanceId is defined as a string in the API metadata. It is an optional parameter and the position is "in":"query".
            List<string> instanceIds = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"];
            request.QueryParam["InstanceId"] = instanceIds; 

            // // Example 2: Call an API operation in the RPC style (Parameter position: "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);
            // // If the request parameters in the API metadata contain the "in": "body" position information, specify the parameters in the body. 
            // request.Body = File.ReadAllBytes(@"D:\test.png");
            // request.Headers["content-type"] = "application/octet-stream";


            // // Example 3: Call an API operation in the RPC style (Parameter position: "in": "formData" or "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);
            // // Request parameters for calling the TranslateGeneral operation:
            // // Context is defined as a string in the API metadata. It is an optional parameter and the position is "in":"query".
            // request.QueryParam["Context"] = "Morning"; 
            // // The position of the FormatType, SourceLanguage, and TargetLanguage parameters in the metadata is defined as "in":"formData".
            // var body = new Dictionary<string, object>
            // {
            //     { "FormatType", "text" },
            //     { "SourceLanguage", "zh" },
            //     { "TargetLanguage", "en" },
            //     { "SourceText", "Hello" },
            //     { "Scene", "general" },
            // };
            // var str = FormDataToString(body);
            // request.Body = Encoding.UTF8.GetBytes(str);
            // request.Headers[ContentType] = "application/x-www-form-urlencoded";


            // // Construct a POST request for a ROA API operation.
            // 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);
            // // The request body. Use JsonConvert to convert the request body into a JSON string.
            // 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";

            // // Construct a GET request for a ROA API operation.
            // String httpMethod = "GET";
            // // If the value of the canonicalUri parameter contains the value of the path parameter, encode the value of the path parameter by specifying percentCode({Value of the path parameter}).
            // String canonicalUri = "/clusters/" + PercentCode("c81d501a467594eab873edbf2XXXXXXXX") + "/resources";
            // String host = "cs.cn-beijing.aliyuncs.com"; 
            // String xAcsAction = "DescribeClusterResources"; 
            // String xAcsVersion = "2015-12-15"; 
            // Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
            // request.QueryParam["with_addon_resources"]=true;

            // // Construct a DELETE request for a ROA API operation.
            // String httpMethod = "DELETE";
            // // If the value of the canonicalUri parameter contains the value of the path parameter, encode the value of the path parameter by specifying percentCode({Value of the path parameter}).
            // String canonicalUri = "/clusters/" + PercentCode("c81d501a467594eab873edbf2XXXXXXXX");
            // String host = "cs.cn-beijing.aliyuncs.com"; 
            // String xAcsAction = "DeleteCluster"; 
            // String xAcsVersion = "2015-12-15"; 
            // Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);

            GetAuthorization(request);
            // Call the API operation.
            var result = CallApiAsync(request);
            Console.WriteLine($"result:{result.Result}");
        }

        private static async Task<string?> CallApiAsync(Request request)
        {
            try
            {
                // Create an HttpClient instance.
                using var httpClient = new HttpClient();

                // Construct a URL.
                string url = $"https://{request.Host}{request.CanonicalUri}";
                var uriBuilder = new UriBuilder(url);
                var query = new List<string>();

                // Configure request parameters.
                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,
                };

                // Configure request headers.
                foreach (var entry in request.Headers)
                {
                    if (entry.Key == "Authorization")
                    {
                        requestMessage.Headers.TryAddWithoutValidation("Authorization", entry.Value.ToString()); ;
                    }
                    else if (entry.Key == ContentType) // The value of the ContentType key must be consistent with that defined in the main method.
                    {
                        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;
                }
                
                // Send a request.
                HttpResponseMessage response = await httpClient.SendAsync(requestMessage);
                // Read the content of the returned response.
                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
            {
                // Flatten the query parameters of the List and Map types.
                request.QueryParam = FlattenDictionary(request.QueryParam);

                // Step 1: Construct a canonicalized request.
                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}");

                // Step 2: Construct a string-to-sign.
                string hashedCanonicalRequest = Sha256Hash(Encoding.UTF8.GetBytes(canonicalRequest));
                string stringToSign = $"{Algorithm}\n{hashedCanonicalRequest}";
                Console.WriteLine($"stringToSign:{stringToSign}");

                // Step 3: Calculate the signature string.
                string signature = HmacSha256(AccessKeySecret, stringToSign);

                // Step 4: Specify the Authorization header.
                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("The specified string cannot be null or empty");
            }
            return Uri.EscapeDataString(str).Replace("+", "%20").Replace("*", "%2A").Replace("%7E", "~");
        }
    }
}

Rust

Catatan

Dalam contoh ini, lingkungan runtime rustc 1.82.0 digunakan. Sesuaikan parameter berdasarkan kebutuhan bisnis Anda.

Untuk menggunakan metode tanda tangan di Rust, Anda harus menambahkan dependensi berikut ke file 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;


// Generate an x-acs-date string.
pub fn current_timestamp() -> Result<u64, SystemTimeError> {
    Ok(SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)?
        .as_secs())
}
// Encode the string in 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) // Return a Cow<str>, which can be a string or a string slice identified by &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
}

/// Calculate the value by using the SHA256 algorithm.
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())
}
// Generate a unique random number for the signature.
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)
}
// Create canonicalized query parameters (after encoding).
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("&")
}
// Read the response.
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))
}
// Define the value type of FormData data.
#[derive(Debug, Clone)]
pub enum FormValue {
    String(String),
    Vec(Vec<String>),
    HashMap(HashMap<String, String>),
}
// Enumerate a set of body types to manage different body types in requests, including JSON, binary, and FormData bodies. 
pub enum RequestBody {
    Json(HashMap<String, Value>), // Json
    Binary(Vec<u8>), // Binary
    FormData(HashMap<String, FormValue>), //  FormData 
    None,
}
// Construct a canonicalized request.
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> {

    // Process the request body based on the body type and store the process content in the body_content variable. 
    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(),
    };
    
    // Calculate the x-acs-content-sha256 value for the request body and prepare the x-acs-date string, the x-acs-signature-nonce string, and the request header of the signature request.
    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);
    // The request header to sign.
    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. Construct a canonicalized request header.
    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. Construct the request header of the signature request.
    let canonical_query_string = build_sored_encoded_query_string(query_params); // Encode and concatenate the parameters.
    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. Calculate the SHA-256 hash value of the request header of the signature request.
    let result = sha256_hex(&canonical_request);
    // 4. Create a string-to-sign.
    let string_to_sign = format!("ACS3-HMAC-SHA256\n{}", result);
    // 5. Calculate the signature.
    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. Build an Authorization header.
    headers.insert("Authorization", HeaderValue::from_str(&auth_data).unwrap());
    // Construct a URL to concatenate request parameters.
    let url: String;
    if !query_params.is_empty() {
        url = format!("https://{}{}?{}", host, canonical_uri,canonical_query_string);
    } else {
        url = format!("https://{}{}", host, canonical_uri);
    }        
    // Call the SDK to send a request.
    let response = send_request(
        &client,
        method,
        &url,
        headers,
        query_params,                
        &body,                      
        &body_content,                
    ) 
    .await?;
    
    // Read the response.
    let (_, res) = read_response(response).await?;
    Ok(res)
}

/// Send the request.
async fn send_request(
    client: &Client,
    method: Method,
    url: &str,
    headers: HeaderMap,
    query_params: &[(&str, &str)],     // Receive the query parameters.
    body: &RequestBody,                // Indicates the body type.
    body_content: &str,                // If the body is not empty, accept the body request parameter, such as FormData, Json, and Binary.
) -> Result<Response, String> {
    let mut request_builder = client.request(method.clone(), url);
    // Add request headers.
    for (k, v) in headers.iter() {
        request_builder = request_builder.header(k, v.clone());
    }
     // Add a request body.
     match body {
        RequestBody::Binary(_) => {
            request_builder = request_builder.header("Content-Type", "application/octet-stream");
            request_builder = request_builder.body(body_content.to_string()); // Change the value based on the scenario.
        }
        RequestBody::Json(_) => {
            // If the body is a map which is not empty, the body is converted to a JSON value stored in the body_content variable. In this case, specify 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(_) => {
            // Configure the content-type variable for parameters whose position is formData.
            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());
        }
    }
    // Construct a request
    let request = request_builder
        .build()
        .map_err(|e| format!("build request fail: {}", e))?;
    // Initiate the request.
    let response = client
        .execute(request)
        .await
        .map_err(|e| format!("execute request fail: {}", e))?;
    // The response parameters.
    Ok(response)
}


 /**
     * 
     * A sample signature. You need to adjust the parameters in the main() method. 
     * <p>
     * Obtain the request method (methods), request parameter names (name), request parameter types (type), and request parameter positions (in). 
     * 1. If the request parameters in the API metadata contain the "in":"query" position information, specify the parameters in the query string (query_params). Note: You can also specify this type of parameter in the body and set content-type to application/x-www-form-urlencoded. For more information, see Example 3. 
     * 2. If the request parameters in the API metadata contain the "in": "body" position information, specify the parameters in the body and set MIME to application/octet-stream or application/json. Note: For RPC APIs, application/json is not recommended; use the method in Example 3 instead. 
     * 3. If the request parameters in the API metadata contain the "in": "formData" position information, specify the parameters in the body and set MIME to application/x-www-form-urlencoded. 
*/
#[tokio::main]
async fn main() {
    // Create an HTTP client.
    let client = Client::new();
    // env::var() specifies that the AccessKey ID and AccessKey secret are obtained from environment variables.
    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;
    
    // Example 1: Send a POST request to call an RPC-style API operation (Parameter position: "in":"query")
    let method=Method::POST; // The request method.
    let host = "ecs.cn-hangzhou.aliyuncs.com"; // endpoint
    let canonical_uri = "/"; // The resource path of an RPC-style API operation is empty. Therefore, a forward slash (/) is used as the value of the CanonicalURI parameter.
    let action = "DescribeInstanceStatus"; // The API operation that you want to perform.
    let version = "2014-05-26"; // The version number of the 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)); 
    }
    // The query parameters.
    let query_params: &[(&str, &str)] = &query;
    // Specify an empty body.
    let body = RequestBody:: None;
    
    // In a POST request for an RPC-style API operation, "in":"query" indicates that the query parameters contain complex data structures.
    // let method = Method::POST; // The request method.
    // let host = "tds.cn-shanghai.aliyuncs.com"; // endpoint
    // let canonical_uri = "/"; // The resource path of an RPC-API operation is empty. Therefore, a forward slash (/) is used as the value of the CanonicalURI parameter.
    // let action = "AddAssetSelectionCriteria"; // The name of the API operation.
    // let version = "2018-12-03"; // The version number of the API.
    // Custom parameters.
    // let mut target_op = HashMap::new();
    // target_op.insert("Operation", "add");
    // target_op.insert("Target", "i-2ze1j7ocdXXXXXXXX");
    // Configure the TargetOperationList parameter and assign map data to the parameter.
    // let target_operation_list = vec![target_op];
    // Tiling parameters.
    // let mut query = flatten_target_ops(target_operation_list, "TargetOperationList");
    // Common parameters.
    // query.push(("SelectionKey", "85a561b7-27d5-47ad-a0ec-XXXXXXXX"));
    // let query_params: &[(&str, &str)] = &query;
    // let body = RequestBody:: None;
    
    // Example 2: Send a POST request to call an RPC-style API operation (Parameter position: "in":"body")
    // let method = Method::POST; // The request method.
    // let host = "ocr-api.cn-hangzhou.aliyuncs.com";
    // let canonical_uri = "/";
    // let action = "RecognizeGeneral";
    // let version = "2021-07-07";
    // "in":"body" of a request parameter indicates a binary file. 
    // let binary_data = std::fs::read("<FILE_PATH>").expect("Failed to read the file"); // Replace <FILE_PATH> with the actual file path.
    // The body is binary.
    // let body = RequestBody::Binary(binary_data);
    // The query parameter is empty.
    // let query_params = &[];

    // Example 3: Send a POST request to call an RPC-style API operation (Parameter position: "in": "formData" or "in":"body")
    // let method = Method::POST; // The request method.
    // let host = "mt.aliyuncs.com";
    // let canonical_uri = "/";
    // let action = "TranslateGeneral";
    // let version = "2018-10-12";
    // // The position of the FormatType, SourceLanguage, and TargetLanguage parameters in the metadata is defined as "in":"formData".
    // let mut form_data = HashMap::new();  // If the body type is FormData(HashMap<String, FormValue>), you can set FormValue to Vec<String>, HashSet<String>, or HashMap<String, String>. If you want to specify more types, enumerate them in the FormValue parameter.
    // form_data.insert(String::from("FormatType"),FormValue::String(String::from("text")));
    // form_data.insert(String::from("SourceLanguage"),FormValue::String(String::from("zh")));
    // form_data.insert(String::from("TargetLanguage"),FormValue::String(String::from("en")));
    // form_data.insert(String::from("SourceText"),FormValue::String(String::from("Hello")));
    // form_data.insert(String::from("Scene"),FormValue::String(String::from("general")));
    // The query parameters.
    // let query_params = &[("Context", "Morning")];
    // The body is FormData and the position information is "in":"formdata".
    // let body = RequestBody::FormData(form_data);

    // Construct a POST request for the CreateClusteroperation in the ROA style to create a cluster.  
    // Specify constants for the API operation.
    // let method = Method::POST; // The request method.
    // let host = "cs.cn-hangzhou.aliyuncs.com";
    // let canonical_uri = "/clusters";
    // let action = "CreateCluster";
    // let version = "2015-12-15";
    // Specify the request body parameters.
    // let mut body_json = HashMap::new();  // If the body type is Json(HashMap<String, Value>), you can set Value to the following values: 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!("Test cluster"));
    // body_json.insert(String::from("region_id"),json!("cn-hangzhou"));
    // body_json.insert(String::from("cluster_type"),json!("ExternalKubernetes"));
    // body_json.insert(String::from("vpcid"),json!("vpc-2zeou1uodXXXXXXXX"));
    // body_json.insert(String::from("container_cidr"),json!("10.X.X.X/X"));
    // body_json.insert(String::from("service_cidr"),json!("10.X.X.X/X"));
    // body_json.insert(String::from("security_group_id"),json!("sg-2ze1a0rlgXXXXXXXX"));
    // body_json.insert(
    //     String::from("vswitch_ids"),
    //     Value::Array(vec![
    //         Value::from("vsw-2zei30dhflXXXXXXXX"),
    //         Value::from("vsw-2zei30dhflXXXXXXXX"),
    //         Value::from("vsw-2zei30dhflXXXXXXXX"),
    //     ]),
    // );
    // The query parameter is empty.
    // let query_params = &[];
    // The body type is JSON. 
    // let body = RequestBody::Json(body_json);

    // Construct a GET request for the DeleteCluster API operation in the ROA style to query resources associated with a specified cluster.
    // let method = Method::GET; // The request method.
    // let host = "cs.cn-hangzhou.aliyuncs.com"; // endpoint
    // //  Concatenated the values to form a resource path.
    // let uri = format!("/clusters/{}/resources", percent_code("ce196d21571a64be9XXXXXXXX").as_ref()); 
    // let canonical_uri = uri.as_str(); // The resource path, which is converted to a string slice identified by &Str.
    // let action = "DescribeClusterResources";   // The API operation that you want to perform.
    // let version = "2015-12-15"; // The version number of the API.
    // // Configure the query parameters.
    // let query_params = &[("with_addon_resources", if true { "true" } else { "false" })];  // "true" or "false"
    // Specify an empty body.
    // let body = RequestBody:: None;

    // Call the API operation in the ROA style to perform the delete operation.   API operation :DeleteCluster. Call the API operation to delete a pay-as-you-go cluster.
    // 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(); // Convert the resource path to a string slice identified by &Str.
    // let action = "DeleteCluster";
    // let version = "2015-12-15";
    // // The query parameters.
    // let query_params = &[];
    // Specify an empty body.
    // let body = RequestBody:: None;
    
    // The API operation (SendSms) that is used to send text messages.
    // let method = Method::POST; // The request method.
    // let host = "dysmsapi.aliyuncs.com"; // endpoint
    // let canonical_uri = "/"; // The resource path of an RPC-API operation is empty. Therefore, a forward slash (/) is used as the value of the CanonicalURI parameter.
    // let action = "SendSms"; // The name of the API operation.
    // let version = "2017-05-25"; // The version number of the 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>"));
    // // The query parameters.
    // let query_params: &[(&str, &str)] = &query;
    // Specify an empty body.
    // let body = RequestBody:: None;

    // Send the request.
    match call_api(
        client.clone(),
        method,                                                  // The request method. Valid values:  POST, GET, and DELETE.                                
        host,                                                    // The endpoint of the API operation.
        canonical_uri,                                           // The resource path.
        query_params,                                            // The position information about the query parameters, which is "in":"query" in this example.
        action,                                                  // The API operation.
        version,                                                 // The version number of the API operation.
        body,                                                    // The position information about the request body, which is "in":"body" in this example. Supported body types: Json, FormData, and Binary.
        access_key_id,                                           
        access_key_secret,
    )
    .await {
        Ok(response) => println!("Response: {}", response),
        Err(error) => eprintln!("Error: {}", error),
    }
}

Shell

#!/bin/bash

accessKey_id="<YOUR-ACCESSKEY-ID>"
accessKey_secret="<YOUR-ACCESSKEY-SECRET>"
algorithm="ACS3-HMAC-SHA256"

# Request parameters -- Adjust the request parameters based on your business requirements.
httpMethod="POST"
host="dns.aliyuncs.com"
queryParam=("DomainName=example.com" "RRKeyWord=@")
action="DescribeDomainRecords"
version="2015-01-09"
canonicalURI="/"
# Parameters of the body or formData type can be specified in the body.
# If the parameter is a body, the body value is a JSON string: "{'key1':'value1','key2':'value2'}". You need to add content-type:application/json; charset=utf-8 to the headers used for signature calculation.
# If the parameter is a binary file, you do not need to modify the body. Add content-type:application/octet-stream to the headers used for signature calculation, and add --data-binary to the curl_command variable.
# If the parameter is a formData parameter, specify the body parameters in the format of "key1=value1&key2=value2", and add content-type:application/x-www-form-urlencoded to the headers used for signature calculation.
body=""

# Specify the time in the ISO 8601 standard in UTC.
utc_timestamp=$(date +%s)
utc_date=$(date -u -d @${utc_timestamp} +"%Y-%m-%dT%H:%M:%SZ") 
# Set the x-acs-signature-nonce header to a random number.
random=$(uuidgen | sed 's/-//g') 

# Add the request headers that are used for signature calculation.
headers="host:${host}
x-acs-action:${action}
x-acs-version:${version}
x-acs-date:${utc_date}
x-acs-signature-nonce:${random}"

# The UTL encoding function.
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}"
}

# Step 1: Construct a canonicalized request.
# Flatten all query parameters.
newQueryParam=()

# Traverse each raw query parameter.
for param in "${queryParam[@]}"; do
    # Check whether the value of a query parameter contains an equal sign (=). If the parameter value contains an equal sign (=), the parameter value is a key-value pair.
    if [[ "$param" == *"="* ]]; then
        # Split the parameter value into a key and value.
        IFS='=' read -r key value <<< "$param"

        # Encode the value in the URL format.
        value=$(urlencode "$value")

        # Check whether the value is a list. If the value is a list, the value is in parentheses ().
        if [[ "$value" =~ ^\(.+\)$ ]]; then
            # Remove the parentheses ().
            value="${value:1:-1}"

            # Use a specific internal field separator (IFS) to split values.
            IFS=' ' read -ra values <<< "$value"

            # Add an index for each value.
            index=1
            for val in "${values[@]}"; do
                # Remove double quotation marks (").
                val="${val%\"}"
                val="${val#\"}"

                # Add the query parameter to a new array.
                newQueryParam+=("$key.$index=$val")
                ((index++))
            done
        else
            # If the value is not a list, add the query parameter to the new array.
            newQueryParam+=("$key=$value")
        fi
    else
        # If the value is not in parentheses, add the query parameter to the new array without modifications.
        newQueryParam+=("$param")
    fi
done

# Process and sort new query parameters.
sortedParams=()
declare -A paramsMap
for param in "${newQueryParam[@]}"; do
    IFS='=' read -r key value <<< "$param"
    paramsMap["$key"]="$value"
done
# Sort query parameters by key.
for key in $(echo ${!paramsMap[@]} | tr ' ' '\n' | LC_ALL=C sort); do
    sortedParams+=("$key=${paramsMap[$key]}")
done

#1.1 Construct a canonicalized query string.
canonicalQueryString=""
first=true
for item in "${sortedParams[@]}"; do
    [ "$first" = true ] && first=false || canonicalQueryString+="&"
    # Check whether an equal sign (=) exists.
    if [[ "$item" == *=* ]]; then
        canonicalQueryString+="$item"
    else
        canonicalQueryString+="$item="
    fi
done

# 1.2 Process the request body.
hashedRequestPayload=$(echo -n "$body" | openssl dgst -sha256 | awk '{print $2}')
headers="${headers}
x-acs-content-sha256:$hashedRequestPayload"

# 1.3 Construct a canonicalized header.
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 Construct a canonicalized request.
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}')
# Step 2: Construct a string-to-sign.
stringToSign="${algorithm}\n${hashedCanonicalRequest}"
echo -e "stringToSign=$stringToSign"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++++"

# Step 3: Calculate the signature string.
signature=$(printf "${stringToSign}" | openssl dgst -sha256 -hmac "${accessKey_secret}" | sed 's/^.* //')
echo -e "signature=${signature}"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++++"

# Step 4: Specify the Authorization header.
authorization="${algorithm} Credential=${accessKey_id},SignedHeaders=${signedHeaders},Signature=${signature}"
echo -e "authorization=${authorization}"

# Construct a cURL command.
url="https://$host$canonicalURI"
curl_command="curl -X $httpMethod '$url?$canonicalQueryString'"

# Add the Authorization header.
IFS=$'\n'  # Use line feed (\n) as the new IFS.
for header in $headers; do
    curl_command="$curl_command -H '$header'"
done
curl_command+=" -H 'Authorization:$authorization'"
# If the body is a binary file, comment out the following line of code.
curl_command+=" -d '$body'"
# If the body is a binary file, uncomment the following line of code.
#curl_command+=" --data-binary @"/root/001.png" "

echo "$curl_command"
# Run the cURL command.
eval "$curl_command"

Bahasa 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() indicates that the AccessKey ID and AccessKey secret are obtained from environmental variables.
#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

// A struct that is used to perform sorting.
typedef struct {
    char key[256];
    char value[256];
} KeyValuePair;

// A comparison function that sorts keys in lexicographic order.
int compare_pairs(const void *a, const void *b) {
    return strcmp(((const KeyValuePair *)a)->key, ((const KeyValuePair *)b)->key);
}

// Encode the value in the URL format.
char* percentEncode(const char* str) {
    if (str == NULL) {
        fprintf(stderr, "The input string cannot be null\n");
        return NULL;
    }
    size_t len = strlen(str);
    char* encoded = (char*)malloc(len * 3 + 1);
    if (encoded == NULL) {
        fprintf(stderr, "Failed to allocate memory resources\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: Encode query parameters in URL, sorts them by lexicographic order, and generates a canonicalized query string.
 * @param query_params: The original query string in the format of "key1=value1&key2=value2".
 * @return char*: The sorted and encoded canonicalized query string. The memory needs to be released by the caller.
 */
char* generate_sorted_encoded_query(const char* query_params) {
    if (query_params == NULL || strlen(query_params) == 0) {
        return strdup(""); // An empty string is returned if parameters are empty.
    }

    KeyValuePair pairs[100]; // A maximum of 100 key-value pairs are supported.
    int pair_count = 0;

    char* copy = strdup(query_params);
    if (!copy) {
        fprintf(stderr, "Failed to allocate memory resources\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);

    // Sort by key.
    qsort(pairs, pair_count, sizeof(KeyValuePair), compare_pairs);

    // Concatenate the sorted query strings.
    char* query_sorted = malloc(BUFFER_SIZE);
    if (!query_sorted) {
        fprintf(stderr, "Failed to allocate memory resources\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;
}

// Perform calculation by using the HMAC-SHA256 algorithm.
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';
}
// Calculate the hash value by using the SHA-256 algorithm.
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';
}
// Generate the x-acs-signature-nonce string.
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; // Retain the highest four values.
    random_bytes[6] |= 0x40; // Set the version to 4.
    random_bytes[8] &= 0x3f; // Retain the highest two values.
    random_bytes[8] |= 0x80; // Set the variant to 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]);
}
// Upload the file.
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; // Failed to read the file.
    }
    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; // Failed to read the file.
    }
    fread(*buffer, 1, file_size, file);
    fclose(file);
    return file_size; // Return the number of bytes that are read.
}
// Calculate the value of the Authorization header.
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) {
    // Prepare the x-acs-signature-nonce string, the x-acs-date string, the x-acs-content-sha256 string, and the string-to-sign.
    generate_uuid(uuid, 37);
    // Specify the x-acs-date string in the yyyy-MM-ddTHH:mm:ssZ format, such as 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);
    // The string-to-sign.
    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. Construct a canonicalized request header.
    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. Construct a request header for the signature request.
    // Sort and encode the query string.
    char* sorted_query_params = generate_sorted_encoded_query(query_params);
    if (!sorted_query_params) {
      fprintf(stderr, "Failed to generate the sorted query string. \n");
      return NULL;
    }
    char canonical_request[BUFFER_SIZE];
    snprintf(canonical_request, sizeof(canonical_request),
         "%s\n%s\n%s\n%s\n\n%s\n%s",
         http_method,
         canonical_uri,
         sorted_query_params ?  sorted_query_params : "",
         canonical_headers,
         signed_headers,
         hashed_payload);
    printf("Canonical Request:\n%s\n", canonical_request);

    // 3. Calculate the hash value of the canonicalized request by using the SHA-256 algorithm.
    char hashed_canonical_request[SHA256_DIGEST_LENGTH * 2 + 1];
    sha256_hex(canonical_request, hashed_canonical_request);
    printf("hashedCanonicalRequest: %s\n", hashed_canonical_request);
    // 4. Create a string-to-sign. 
    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. Calculate the signature. 
    char signature[SHA256_DIGEST_LENGTH * 2 + 1];
    hmac256(ACCESS_KEY_SECRET, string_to_sign, signature);
    printf("Signature: %s\n", signature);
    // 6. Construct an Authorization header.
    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;
}
// Send a request.
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) {
    // Obtain the parameter values required by the signature.
    char authorization_header[BUFFER_SIZE];
    char hashed_payload[SHA256_DIGEST_LENGTH * 2 + 1];
    char x_acs_date[64];
    char uuid[37];
    // 1. Initialize curl.
    CURL *curl = curl_easy_init();
    if (!curl) {
        fprintf(stderr, "curl_easy_init() failed\n");
        goto cleanup;
    }
    // 2. Calculate the signature. The sorted and encoded query string is returned.
    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. Add request parameters. 
    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);
    // Release the memory.
    if (signed_query_params) {
        free(signed_query_params); // Release the memory.
    }

    // 4. Add request 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);
    // Disable SSL verification in other curl settings, and add debugging information.
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
    // 5. Add  a request 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. Send the request.
    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);
}
/**
*
     * A sample signature. You need to adjust the parameters in the main() method. 
     * <p>
     * Obtain the request method (methods), request parameter names (name), request parameter types (type), and request parameter positions (in).
     *1. If the request parameters in the API metadata contain the "in":"query" position information, specify the parameters in the query string (query_params). Note: You can also specify this type of parameter in the body and set content-type to application/x-www-form-urlencoded. For more information, see Example 3. 
     *2. If the request parameters in the API metadata contain the "in": "body" position information, specify the parameters in the body and set MIME to application/octet-stream or application/json. Note: For RPC APIs, application/json is not recommended; use the method in Example 3 instead. 
     3. If the request parameters in the API metadata contain the "in": "formData" position information, specify the parameters in the body and set MIME to application/x-www-form-urlencoded. 
*/
int main() {
    // Set the response format to UTF-8.
    SetConsoleOutputCP(CP_UTF8);
    srand((unsigned int)time(NULL));

    /**
      * Example: Call an RPC-style API operation whose query parameters contain complex data structures (Parameter position: "in":"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";

    // Configure the SelectionKey parameter to specify a string type.
    const char *selection_key = "85a561b7-27d5-47ad-a0ec-XXXXXXXX";
    // Configure the TargetOperationList parameter to specfy a list of objects.
    struct {
        const char *operation;
        const char *target;
    } targetOperation_list[] = {
        {"add", "i-2ze1j7ocdg9XXXXXXXX"},
        // You can add multiple entries.
        // {"add", "i-abc123xyzXXXXX"},
    };

    int count = sizeof(targetOperation_list) / sizeof(targetOperation_list[0]);
    KeyValuePair pairs[100]; // Store the original key and 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++;
}
    // Add the SelectionKey parameter.
    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++;

    // Sorting and coding are performed by get_authorization().
    qsort(pairs, pair_count, sizeof(KeyValuePair), compare_pairs);

    // Construct the original query string (unencoded).
    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 = ""; // Specify an empty 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));

    /**
      * Example: Call an API operation in the RPC style (Parameter position: "in":"query")
    */
    // Specify the request parameters for the API operation.
    // 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";
    // // Configure the InstanceId parameter, which is optional. The value is an array.
    // const char *instance_ids[] = {
    //     "i-bp11ht4hXXXXXXXX",
    //     "i-bp16maz3XXXXXXXX"
    // };
    // // Concatenate the InstanceId array.
    // char InstanceId[BUFFER_SIZE];
    // snprintf(InstanceId, sizeof(InstanceId),
    //          "InstanceId.1=%s&InstanceId.2=%s",
    //         instance_ids[0],
    //         instance_ids[1]);
    // // Specify the query parameters. Required parameters: RegionId=cn-hangzhou and 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));

      /**
        * Example: Call an API operation in the RPC style (Parameter position: "in":"body")
      */
    // Declare the pointer for storing the file content read by the system.
    // 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");
    // }

      /**
       * Example: Call an API operation in the RPC style (Parameter position: "in": "formData" or "in":"body")
       */
    // const char *http_method = "POST";
    // const char *canonical_uri = "/";
    // const char *host = "mt.aliyuncs.com";
    // const char *x_acs_action = "TranslateGeneral";
    // const char *x_acs_version = "2018-10-12";
    // char query_params[BUFFER_SIZE];
    // snprintf(query_params, sizeof(query_params), "Context=%s", "Morning");
    // const char *format_type = "text";
    // const char *source_language = "zh";
    // const char *target_language = "en";
    // const char *source_text = "Hello";
    // const char *scene = "general";
    // char body[BUFFER_SIZE];
    // snprintf(body, sizeof(body),
    // "FormatType=%s&SourceLanguage=%s&TargetLanguage=%s&SourceText=%s&Scene=%s",
    // percentEncode(format_type), percentEncode(source_language), percentEncode(target_language),
    // percentEncode(source_text), percentEncode(scene));
    // const char *content_type = "application/x-www-form-urlencoded";
    // printf("formdate_body: %s\n", body);
    // call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));

   // Example 3: Call an API operation in the RPC style (Parameter position: "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 = "Who are you";
//    const char *Application = "sddp_pre";
//    char body[BUFFER_SIZE];
//    snprintf(body, sizeof(body),
//            "Memory=%s&Stream=%s&ProductCode=%s&Feature=%s&Model=%s&Type=%s&TopP=%s&Temperature=%s&Prompt=%s&Application=%s",
//            Memory, Stream, ProductCode, Feature, Model, Type, TopP, Temperature, Prompt, Application);
//    const char *content_type = "application/x-www-form-urlencoded";
//    printf("formdate_body: %s\n", body);
//    call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));

      /**
        * Construct a POST request for an ROA-style API operation whose parameter position is "in" "body".
      */
//    const char *http_method = "POST";
//    const char *canonical_uri = "/clusters";
//    const char *host = "cs.cn-beijing.aliyuncs.com";
//    const char *x_acs_action = "CreateCluster";
//    const char *x_acs_version = "2015-12-15";
//    const char *query_params = "";
//    char body[BUFFER_SIZE];
//    snprintf(body, sizeof(body),
//             "{\"name\":\"%s\",\"region_id\":\"%s\",\"cluster_type\":\"%s\","
//             "\"vpcid\":\"%s\",\"container_cidr\":\"%s\","
//             "\"service_cidr\":\"%s\",\"security_group_id\":\"%s\","
//             "\"vswitch_ids\":[\"%s\"]}",
//             "Test cluster", "cn-beijing", "ExternalKubernetes",
//             "vpc-2zeou1uod4yXXXXXXXX", "10.X.X.X/XX",
//             "10.X.X.X/XX", "sg-2ze1a0rlgeXXXXXXXX",
//             "vsw-2zei30dhflXXXXXXXX");
//    const char *content_type = "application/json; charset=utf-8";
//    call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));

      /**
        * Construct a GET request for an API operation in the ROA style.
      */
//    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));

      /**
        * Construct a DELETE request for an API operation in the ROA style.
      */
//    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));



    // Store the generated values in variables.
    char authorization_header[BUFFER_SIZE];
    char hashed_payload[SHA256_DIGEST_LENGTH * 2 + 1];
    char x_acs_date[64];
    char uuid[37];
    return 0;
}

FAQ

Apa yang harus saya lakukan jika tanda tangan gagal diverifikasi dan pesan kesalahan "Tanda tangan yang ditentukan tidak cocok dengan perhitungan kami" atau "Tanda tangan permintaan tidak sesuai dengan standar Aliyun" dikembalikan?

Dalam kebanyakan kasus, tanda tangan gagal diverifikasi karena operasi yang diperlukan tidak dilakukan selama proses perhitungan tanda tangan.

Penyebab umum:

  • Kesalahan konfigurasi AccessKey.

  • Posisi parameter salah: Misalnya, melewatkan parameter kueri dalam badan permintaan.

  • Parameter dalam string query yang dinormalisasi (CanonicalQueryString) tidak diurutkan secara alfabetis.

  • Header yang digunakan untuk perhitungan tanda tangan (SignedHeaders) tidak diurutkan secara alfabetis.

  • Karakter spasi tidak dienkripsi menjadi %20.

  • Parameter dikodekan URL lebih dari sekali. Selama proses perhitungan tanda tangan, pengkodean URL hanya boleh diterapkan sekali pada parameter jalur dan string kueri yang dikanonisasi. Misalnya, jika pesan kesalahan Anda menunjukkan beberapa karakter %25, itu menunjukkan bahwa karakter % itu sendiri telah dikodekan secara salah.

Solusi:

Catatan

Periksa apakah hasil perhitungan tanda tangan benar di mesin lokal Anda. Untuk informasi lebih lanjut, lihat bagian Nilai parameter tetap dalam topik ini.

  1. Pertama, bandingkan apakah nilai CanonicalRequest sama dengan hasil yang Anda hitung di mesin lokal Anda. Jika tidak, lihat Langkah 1: Membangun permintaan yang dikanonisasi dalam topik ini untuk menemukan perbedaan konfigurasi antara kode contoh dan kode Anda.

  2. Jika nilai CanonicalRequest sama dengan hasil yang Anda hitung di mesin lokal Anda, bandingkan apakah nilai StringToSign sama dengan hasil yang Anda hitung di mesin lokal Anda. Jika tidak konsisten, mungkin ada kesalahan dalam algoritma hash Anda.

  3. Jika CanonicalRequest dan StringToSign cocok, masalahnya kemungkinan besar salah satu dari berikut ini: rahasia AccessKey Anda salah, atau algoritma enkripsi diimplementasikan secara salah.

  4. Jika masalah tetap berlanjut setelah memeriksa semua hal di atas, minta dukungan teknis.

Bagaimana cara menggunakan Postman untuk debugging?

Jika Anda tidak dapat menggunakan Postman untuk memanggil operasi API dan ingin menggunakan Postman untuk debugging, lakukan langkah-langkah berikut:

  1. Gunakan kode atau skrip Anda sendiri untuk menghitung tanda tangan dan menghasilkan nilai header Authorization sesuai dengan mekanisme tanda tangan.

  2. Salin semua header permintaan dari CanonicalHeaders Anda (termasuk header Authorization yang baru saja Anda hasilkan) ke tab Headers di Postman. Contoh:

    Kunci

    Nilai contoh

    host

    dysmsapi.aliyuncs.com

    x-acs-action

    SendSms

    x-acs-content-sha256

    e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

    x-acs-date

    2025-04-16T07:45:55Z

    x-acs-signature-nonce

    315484d3-b129-4966-974a-699b7ee56647

    x-acs-version

    2017-05-25

    Authorization

    ACS3-HMAC-SHA256 Credential=testAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=b37aac99faa507472778256374366b7a47ba48adbc484a53ad789db194658a2d

  3. Tambahkan parameter API ke Postman sesuai dengan tipenya.

    Catatan

    Urutan parameter harus identik dengan urutan yang digunakan saat Anda menghitung tanda tangan.

    • Untuk parameter kueri, tambahkan di tab Params.

    • Untuk parameter badan, tambahkan di tab Body.

Bagaimana cara melewatkan parameter permintaan?

Dalam metadata API, bidang in menentukan lokasi setiap parameter, yang menentukan cara parameter dilewatkan dalam permintaan.

Posisi

Deskripsi

content-type

"in": "query"

Parameter kueri muncul di URL setelah tanda tanya (?). Setiap pasangan kunci-nilai dipisahkan oleh tanda dan (&).

Opsional. Jika disediakan, nilainya harus application/json.

"in": "formData"

Parameter formulir dikirim dalam badan permintaan. Parameter tersebut harus digabungkan menjadi string dengan format key1=value1&key2=value2&key3=value3. Catatan: Untuk tipe kompleks seperti array atau objek, Anda harus "meratakan" nilai menjadi pasangan kunci-nilai yang diindeks. Misalnya, objek seperti {"key":["value1","value2"]} harus dikonversi menjadi {"key.1":"value1","key.2":"value2"}.

Wajib. Nilainya harus application/x-www-form-urlencoded.

"in": "body"

Parameter badan dilewatkan langsung dalam badan permintaan.

Wajib. Nilai Content-Type tergantung pada jenis konten yang dikirim. Misalnya:

  • Jika konten permintaan adalah string JSON, tetapkan nilai header Content-Type ke application/json.

  • Jika konten permintaan adalah aliran file biner, tetapkan nilai header Content-Type ke application/octet-stream.

Sesuai metadata API, nilai style adalah RPC atau ROA.

Nilai style hanya memengaruhi nilai parameter CanonicalURI. Jika Anda menetapkan style ke nilai selain RPC dan ROA, lihat metadata API untuk memeriksa apakah parameter path didefinisikan. Jika ya, tetapkan CanonicalURI ke nilai path. Jika tidak, tetapkan CanonicalURI ke garis miring (/). Misalnya, jika nilai path didefinisikan sebagai /api/v1/clusters dalam metadata operasi API yang digunakan untuk mengkueri kluster ACK, tetapkan CanonicalURI ke /api/v1/clusters.image

Bagaimana cara melewatkan parameter bertipe array atau objek?

Saat melewatkan tipe data kompleks seperti array atau objek, Anda harus mengonversinya menjadi pasangan kunci-nilai yang diindeks.

Contoh 1: {"InstanceId":["i-bp10igfmnyttXXXXXXXX","i-bp1incuofvzxXXXXXXXX","i-bp1incuofvzxXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX"]} harus dikonversi menjadi:

{
    "InstanceId.1": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.10": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.11": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.12": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.2": "i-bp1incuofvzxXXXXXXXX",
    "InstanceId.3": "i-bp1incuofvzxXXXXXXXX",
    "InstanceId.4": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.5": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.6": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.7": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.8": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.9": "i-bp10igfmnyttXXXXXXXX"
}

Contoh 2: {"ImageId":"win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd","RegionId":"cn-shanghai","Tag":[{"tag1":"value1","tag2":"value2"}]} harus dikonversi menjadi:

{
    "ImageId":"win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd",
    "RegionId":"cn-shanghai",
    "Tag.1.tag1":"value1",
    "Tag.1.tag2":"value2"
}

Bagaimana cara memperoleh nilai x-acs-version untuk versi API?

  1. Masuk ke OpenAPI Portal dan pilih API yang ingin Anda panggil. Sebagai contoh, API ECS digunakan.image

  2. Lihat versi API yang direkomendasikan di halaman utama produk. Sebagai contoh, versi API yang direkomendasikan untuk ECS adalah 2014-05-26.

    image

Dalam mode tanda tangan sendiri, jika saya menggunakan metode GET untuk memanggil operasi API selama debugging dan pemanggilan berhasil, dapatkah saya menggunakan metode POST untuk memanggil operasi API?

  • Dalam kebanyakan kasus, Anda dapat menggunakan metode POST atau GET untuk memanggil operasi API bergaya RPC.

  • Namun, Anda hanya dapat menggunakan satu metode untuk memanggil operasi API bergaya ROA.

Untuk informasi lebih lanjut tentang metode permintaan yang dapat digunakan untuk memanggil operasi API, lihat metadata API.

Apa yang harus saya lakukan jika pesan kesalahan "Anda tidak berwenang untuk melakukan operasi ini." dikembalikan?

Penyebab: Pasangan AccessKey yang digunakan oleh pengguna Resource Access Management (RAM) tidak memiliki izin untuk memanggil operasi API.

Solusi: Lihat Apa yang harus saya lakukan jika pesan kesalahan "kode 403. Anda tidak berwenang untuk melakukan operasi ini. Action: xxxx" dikembalikan?

Bagaimana cara memperoleh pasangan AccessKey?

Pasangan AccessKey adalah kredensial akses permanen yang disediakan oleh Alibaba Cloud kepada pengguna. Pasangan AccessKey terdiri dari ID AccessKey dan rahasia AccessKey. Saat Anda mengakses sumber daya Alibaba Cloud dengan memanggil operasi API, sistem mengautentikasi identitas pemanggil dan validitas permintaan berdasarkan ID AccessKey dan tanda tangan yang dihasilkan berdasarkan rahasia AccessKey. Untuk informasi lebih lanjut tentang cara memperoleh pasangan AccessKey, lihat Buat AccessKey untuk pengguna RAM.

Hubungi kami

Jika Anda memiliki pertanyaan selama proses perhitungan tanda tangan, bergabunglah dengan grup DingTalk (ID: 147535001692) untuk meminta dukungan teknis.

Catatan

Jangan bergabung dengan grup ini untuk pertanyaan lain.