Todos os produtos
Search
Central de documentação

Alibaba Cloud SDK:Estrutura e assinatura de requisições V3

Última atualização: Jul 02, 2026

Se você não puder ou preferir não usar um SDK da Alibaba Cloud, assine as requisições HTTP manualmente para chamar operações de API. Este tópico explica a construção da assinatura V3 e a assinatura de requisições.

Observações de uso

  • Se você usa o método de assinatura V2, migre para a versão V3.

  • A assinatura V3 aplica-se aos serviços da Alibaba Cloud cujos SDKs estão disponíveis no OpenAPI Explorer. Alguns serviços usam gateways autogerenciados com autenticação diferente. Consulte a documentação de assinatura específica do serviço antes de enviar requisições HTTP.

Sintaxe da requisição HTTP

Uma requisição de API da Alibaba Cloud consiste nos seguintes componentes.

Componente

Obrigatório

Descrição

Exemplo

Protocolo

Sim

Protocolo da requisição: HTTP ou HTTPS. Use HTTPS para garantir a segurança. Valores válidos: https:// e http://. Verifique os protocolos suportados na referência da API.

https://

Endpoint

Sim

Endpoint da API do serviço. Consulte a referência da API para obter endpoints específicos por região.

ecs.cn-shanghai.aliyuncs.com

resource_URI_parameters

Sim

URL do recurso, incluindo o caminho e os parâmetros da requisição.

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

RequestHeader

Sim

Cabeçalhos comuns da requisição contendo a versão da API, o endpoint e informações de autenticação. Consulte a seção RequestHeader abaixo.

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

Sim

Parâmetros do corpo da requisição. Obtenha detalhes dos parâmetros nos metadados do OpenAPI.

HTTPMethod

Sim

Método HTTP. Obtenha o método nos metadados do OpenAPI.

POST

RequestHeader

Inclua os seguintes cabeçalhos em cada requisição de API.

Cabeçalho

Tipo

Obrigatório

Descrição

Exemplo

host

String

Sim

Endpoint da API do serviço da Alibaba Cloud. Consulte Sintaxe da requisição HTTP.

ecs.cn-shanghai.aliyuncs.com

x-acs-action

String

Sim

Operação de API a ser chamada. Encontre as operações disponíveis no OpenAPI Portal.

RunInstances

x-acs-content-sha256

String

Sim

Hash codificado em Base16 do corpo da requisição. Deve corresponder ao valor de HashedRequestPayload.

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

x-acs-date

String

Sim

Timestamp da requisição no formato ISO 8601 (aaaa-MM-ddTHH:mm:ssZ, UTC). Deve estar dentro de 15 minutos do momento de envio da requisição.

2023-10-26T10:22:32Z

x-acs-signature-nonce

String

Sim

Número aleatório único para evitar ataques de replay. Use um valor diferente para cada requisição. Aplica-se apenas a HTTP.

3156853299f313e23d1673dc12e1703d

x-acs-version

String

Sim

Número da versão da API. Consulte Como obter o valor de x-acs-version.

2014-05-26

Authorization

String

Sim, se a requisição não for anônima

Informações de autenticação para validação da requisição. Formato: Authorization: SignatureAlgorithm Credential=AccessKeyId,SignedHeaders=SignedHeaders,Signature=Signature.

SignatureAlgorithm: método de criptografia da string de assinatura. Defina o valor como ACS3-HMAC-SHA256.

Credential: seu AccessKey ID. Visualize-o no RAM console. Crie um par de AccessKey.

SignedHeaders: cabeçalhos da requisição usados para cálculo da assinatura. Use todos os cabeçalhos comuns da requisição, exceto Authorization.

Signature: assinatura da requisição. Consulte a seção Método de assinatura abaixo.

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

Sim, se o Security Token Service (STS) for usado para autenticação

Token STS. Use o valor SecurityToken da resposta de AssumeRole.

Método de assinatura

A Alibaba Cloud assina cada requisição HTTP/HTTPS usando seu AccessKey ID e AccessKey secret e compara a assinatura gerada com a presente na requisição para autenticar o chamador.

Importante

Todas as requisições e respostas são codificadas em UTF-8.

Etapa 1: Construir uma requisição canônica

Construa uma requisição canônica usando o seguinte pseudocódigo:

CanonicalRequest =
  HTTPRequestMethod + '\n' +    // The HTTP request method in uppercase letters.
  CanonicalURI + '\n' +         // The canonicalized uniform resource identifier (URI).
  CanonicalQueryString + '\n' + // The canonicalized query string.
  CanonicalHeaders + '\n' +     // The canonicalized request headers.
  SignedHeaders + '\n' +        // The request headers that are used for signature calculation.
  HashedRequestPayload // The hash value of the request body.

Método da requisição (HTTPRequestMethod)

Método da requisição HTTP em letras maiúsculas, como GET ou POST.

URI canônico (CanonicalURI)

Caminho do recurso codificado por URI entre o endpoint e a string de consulta, incluindo a barra inicial /, mas excluindo o ponto de interrogação ?. Codifique cada segmento de caminho separado por / em UTF-8 conforme a RFC 3986. Regras de codificação:

  • Letras, dígitos, hifens (-), sublinhados (_), pontos (.) e tis (~) não precisam ser codificados.

    Outros caracteres devem ser codificados por porcentagem no seguinte formato: % + código ASCII dos caracteres em notação hexadecimal. Por exemplo, aspas duplas (") são codificadas como %22. A tabela a seguir descreve alguns caracteres especiais antes e depois da codificação. Atenha-se a esses caracteres especiais.

    Antes da codificação

    Após a codificação

    Caracteres de espaço ( )

    %20

    Asteriscos (*)

    %2A

    %7E

    Tis (~)

Ao usar java.net.URLEncoder da biblioteca padrão Java, execute encode nas strings com base nessa biblioteca. Nas strings codificadas, substitua sinais de mais (+) por %20, asteriscos (*) por %2A e %7E por tis (~). Assim, você obtém strings codificadas que correspondem às regras de codificação anteriores.

Importante

Se o estilo da API for RPC, use uma barra (/) como valor do parâmetro CanonicalURI.

Se o estilo da API for ROA, codifique o valor do parâmetro path nos metadados da operação de API e use o valor codificado como valor do parâmetro CanonicalURI. Exemplo: /api/v1/clusters.

String de consulta canônica (CanonicalQueryString)

Nos metadados da API, caso os parâmetros da requisição contenham a informação de posição "in":"query", concatene os parâmetros conforme o método a seguir:

  1. Ordene todos os parâmetros da requisição alfabeticamente pelo nome do parâmetro.

  2. Codifique os nomes e valores dos parâmetros em UTF-8 com base na RFC 3986. As regras de codificação são as mesmas usadas para construir o URI canônico.

  3. Use um sinal de igual (=) para concatenar o nome e o valor codificados de cada parâmetro. Para parâmetros sem valor, use uma string vazia.

  4. Use "e" comercial (&) para concatenar os parâmetros codificados na ordem obtida na etapa anterior.

Importante

Exemplo:

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

HashedRequestPayload

Use a função de hash para converter o corpo da requisição e codifique o valor do hash em Base16 para gerar o HashedRequestPayload. Em seguida, altere o valor de x-acs-content-sha256 no cabeçalho da requisição para o valor de HashedRequestPayload. Pseudocódigo:

HashedRequestPayload = HexEncode(Hash(RequestBody))
  • Nos metadados da API, se os parâmetros da requisição contiverem a informação de posição "in": "body" ou "in": "formData", especifique os parâmetros no corpo da requisição.

    Nota

    Se a requisição não tiver corpo, defina o valor de RequestBody como uma string vazia.

    • Caso os parâmetros da requisição contenham a informação de posição "in": "formData", concatene-os em uma string no formato: key1=value1&key2=value2&key3=value3. Adicione também o cabeçalho Content-Type e defina-o como application/x-www-form-urlencoded. Por exemplo, se os parâmetros da requisição forem do tipo array ou objeto, converta seus valores em pares chave-valor indexados.

    • Quando os parâmetros da requisição contiverem a informação de posição "in": "body", adicione o cabeçalho Content-Type à requisição. O valor desse cabeçalho especifica o tipo de conteúdo da requisição. Exemplo:

      • Para conteúdo de requisição em string JSON, defina o valor do cabeçalho Content-Type como application/json.

      • Para fluxo de arquivo binário, defina o valor do cabeçalho Content-Type como application/octet-stream.

  • Hash() especifica uma função de hash. Apenas o algoritmo SHA-256 é suportado.

  • HexEncode() codifica o valor do hash em Base16. Esta função retorna um valor de hash codificado em formato hexadecimal com letras minúsculas.

  • Valor de exemplo quando o corpo da requisição está vazio:

    e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

    Cabeçalhos canônicos (CanonicalHeaders)

    Concatene os parâmetros nos cabeçalhos da requisição conforme o método a seguir:

    1. Filtre os cabeçalhos da requisição que começam com x-acs-, além dos cabeçalhos Host e Content-Type.

    2. Converta os nomes dos cabeçalhos para minúsculas e ordene-os alfabeticamente.

    3. Remova os espaços antes e depois do valor de cada cabeçalho.

    4. Concatene o nome e o valor do cabeçalho usando dois pontos (:) e adicione uma quebra de linha (\n) ao final do par nome-valor para formar uma entrada de cabeçalho canônico.

    5. Concatene múltiplas entradas de cabeçalho canônico em uma única string.

    Nota

    Todos os cabeçalhos da requisição, exceto Authorization, devem ser usados para o cálculo da assinatura.

    Pseudocódigo:

    CanonicalHeaderEntry = Lowercase(HeaderName) + ':' + Trim(HeaderValue) + '\n'
    
    CanonicalHeaders = 
        CanonicalHeaderEntry0 + CanonicalHeaderEntry1 + ... + CanonicalHeaderEntryN

    Exemplo:

    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

    Cabeçalhos assinados (SignedHeaders)

    Nomes dos cabeçalhos em minúsculas, separados por ponto e vírgula, usados para o cálculo da assinatura. Construa os cabeçalhos assinados da seguinte forma:

    • Converta os nomes dos cabeçalhos canônicos para minúsculas.

    • Ordene os cabeçalhos alfabeticamente e use ponto e vírgula (;) para separá-los.

      Pseudocódigo:

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

    Exemplo:

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

    Etapa 2: Construir a string a ser assinada

    Construa a string a ser assinada usando o seguinte pseudocódigo:

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

      Apenas o algoritmo ACS3-HMAC-SHA256 é suportado para cálculo de assinatura.

    • HashedCanonicalRequest

      Valor do hash da requisição canônica. O pseudocódigo a seguir mostra como gerar esse valor:

      HashedCanonicalRequest = HexEncode(Hash(CanonicalRequest))
      • Hash() especifica uma função de hash. Apenas o algoritmo SHA-256 é suportado.

      • HexEncode() codifica o valor do hash em Base16. Esta função retorna um valor de hash codificado em formato hexadecimal com letras minúsculas.

    Exemplo:

    ACS3-HMAC-SHA256
    7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259

    Etapa 3: Calcular a string de assinatura

    Calcule a assinatura usando o seguinte pseudocódigo:

    Signature = HexEncode(SignatureMethod(Secret, StringToSign))
    • StringToSign: string a ser assinada construída na Etapa 2. O valor é codificado em UTF-8.

    • SignatureMethod: Especifique HMAC-SHA256 como algoritmo de assinatura.

    • Secret: AccessKey secret.

    • HexEncode: função usada para codificar valores em Base16.

    Exemplo:

    06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0

    Etapa 4: Adicionar a string de assinatura à requisição

    Após obter a string de assinatura, especifique o cabeçalho Authorization na requisição no seguinte formato: Authorization:<SignatureAlgorithm> Credential=<AccessKeyId>,SignedHeaders=<SignedHeaders>,Signature=<Signature>.

    Exemplo:

    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

    Exemplos de assinatura

    Para ajudar você a compreender o método de assinatura descrito anteriormente, esta seção fornece códigos de exemplo que implementam completamente o processo nas principais linguagens de programação. Estes exemplos servem apenas para fins didáticos e não são universalmente aplicáveis. A Alibaba Cloud oferece SDKs para diversas linguagens e frameworks de desenvolvimento. Recomendamos o uso dos SDKs da Alibaba Cloud para fazer requisições de API e gerar assinaturas automaticamente. Assim, você desenvolve aplicações na Alibaba Cloud sem precisar calcular a assinatura manualmente.

    Importante

    Antes de assinar uma requisição, leia e compreenda os metadados da OpenAPI, o método de requisição da API, os parâmetros de requisição, os tipos desses parâmetros e como eles são transmitidos. Caso contrário, a assinatura das requisições pode falhar.

    Valores fixos de parâmetros

    Neste exemplo, valores de amostra demonstram como os resultados são gerados em cada etapa. Use esses valores para simular cálculos e compare seu resultado com o resultado deste exemplo para validar o processo de assinatura.

    Parâmetro

    Exemplo

    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

    Parâmetros específicos da operação

    ImageId

    win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd

    RegionId

    cn-shanghai

    Siga as etapas abaixo para assinar uma requisição:

    1. Construa uma requisição canônica.

    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. Construa a string a ser assinada.

    ACS3-HMAC-SHA256
    7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259
    1. Calcule a string de assinatura.

    06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0
    1. Adicione a string de assinatura à requisição.

    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

    Nota

    Este exemplo usa o ambiente de execução JDK 1.8. Ajuste os parâmetros conforme as necessidades do seu negócio.

    Para usar o método de assinatura em Java, adicione as seguintes dependências Maven ao arquivo 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

    Nota

    Este exemplo usa o ambiente de execução Python 3.12.3. Ajuste os parâmetros conforme as necessidades do seu negócio.

    Instale manualmente as bibliotecas pytz e requests. Execute os comandos abaixo no terminal de acordo com sua versão do Python:

    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:
            """Sorts the query parameters alphabetically by name."""
            self.query_param = dict(sorted(self.query_param.items()))
    
        def sorted_headers(self) -> None:
            """Sorts the request headers alphabetically by name."""
            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: Create a canonical 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: Create the 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.
            signature = hmac256(ACCESS_KEY_SECRET.encode("utf-8"), string_to_sign).hex().lower()
    
            # Step 4: Add the signature to 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", "~")
    
    # Get the access key ID and access key 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 script provides examples of API signature calculation. To run a test, uncomment an example in the main function and modify its values.
    The primary difference between ROA and RPC APIs is how the `canonical_uri` is constructed.
    
    Get the request method, parameter names, types, and locations from the API metadata, and use them to initialize a `SignatureRequest` object.
    1. If a parameter's location is "in": "query" in the API metadata, add it to the `query_param` dictionary. You do not need to set the content-type. Note: For RPC APIs, you can also pass these parameters in the request body with a content-type of `application/x-www-form-urlencoded`. See Example 3.
    2. If a parameter's location is "in": "body", pass it in the request body. Set the `content-type` header based on the payload format.
    3. If a parameter's location is "in": "formData", pass it in the request body and set the `content-type` header to `application/x-www-form-urlencoded`.
    """
    if __name__ == "__main__":
        # Example 1: RPC API request with parameters in the query
        http_method = "POST"  # The request method, which you can get from the API metadata. We recommend that you use POST.
        canonical_uri = "/"  # For RPC APIs, the resource path is empty. Use a forward slash (/) for the `canonical_uri`.
        host = "ecs.cn-hangzhou.aliyuncs.com"  # The service endpoint.
        x_acs_action = "DescribeInstanceStatus"  # The API operation.
        x_acs_version = "2014-05-26"  # The API version.
        signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
        # The request parameters for DescribeInstanceStatus are as follows:
        # In the API metadata, `RegionId` is a required string in the query.
        signature_request.query_param['RegionId'] = 'cn-hangzhou'
        # In the API metadata, `InstanceId` is an optional array in the query.
        signature_request.query_param['InstanceId'] = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX",
                                                       "i-bp1incuofvzxXXXXXXXX"]
    
        # # Example 2: RPC API request with parameters in the body (file upload scenario)
        # 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)
        # # This parameter is passed in the request body, as specified by "in": "body" in the API metadata.
        # 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: RPC API request with parameters in "formData" or the body (non-file upload scenario)
        # 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)
        # # The request parameters for TranslateGeneral are as follows:
        # # In the API metadata, `Context` is an optional string in the query.
        # signature_request.query_param['Context'] = 'Morning'
        # # Parameters such as `FormatType`, `SourceLanguage`, and `TargetLanguage` are located in `formData` in the API metadata.
        # 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: ROA API POST request
        # 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)
        # # This parameter is passed in the request body, as specified by "in": "body" in the API metadata.
        # 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: ROA API GET request
        # http_method = "GET"
        # # If `canonical_uri` contains a path parameter, encode it by using the `percent_code()` function.
        # 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: ROA API DELETE request
        # http_method = "DELETE"
        # # If `canonical_uri` contains a path parameter, encode it by using the `percent_code()` function.
        # 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

    Nota

    Este exemplo usa o ambiente de execução go1.22.2. Ajuste os parâmetros conforme as necessidades do seu negócio.

    Execute os seguintes comandos no 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

    Nota

    Este exemplo usa o ambiente de execução Node.js v20.13.1. Ajuste os parâmetros conforme as necessidades do seu negócio.

    Este exemplo usa Node.js.

    const crypto = require('crypto');
    const fs = require('fs');
    
    class Request {
        constructor(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion) {
            this.httpMethod = httpMethod;
            this.canonicalUri = canonicalUri || '/';
            this.host = host;
            this.xAcsAction = xAcsAction;
            this.xAcsVersion = xAcsVersion;
            this.headers = {};
            this.body = null;
            this.queryParam = {};
            this.initHeader();
        }
    
        initHeader() {
            const date = new Date();
            this.headers = {
                'host': this.host,
                'x-acs-action': this.xAcsAction,
                'x-acs-version': this.xAcsVersion,
                'x-acs-date': date.toISOString().replace(/\..+/, 'Z'),
                'x-acs-signature-nonce': crypto.randomBytes(16).toString('hex')
            }
        }
    }
    
    const ALGORITHM = 'ACS3-HMAC-SHA256';
    const accessKeyId = process.env.ALIBABA_CLOUD_ACCESS_KEY_ID;
    const accessKeySecret = process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET;
    const securityToken = process.env.ALIBABA_CLOUD_SECURITY_TOKEN;
    const encoder = new TextEncoder()
    
    if (!accessKeyId || !accessKeySecret) {
        console.error('ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables must be set.');
        process.exit(1);
    }
    
    function getAuthorization(signRequest) {
        try {
            newQueryParam = {};
            processObject(newQueryParam, "", signRequest.queryParam);
            signRequest.queryParam = newQueryParam;
            // Step 1: Construct the 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 for the payload.
            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 list of signed headers. Header names are lowercase, sorted alphabetically, and separated by semicolons (;).
            const signedHeaders = sortedKeys.join(";")
            // The canonical headers. Concatenate multiple canonicalized headers, sorted alphabetically by lowercase header name.
            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 the string-to-sign.
            const hashedCanonicalRequest = sha256Hex(encoder.encode(canonicalRequest));
            const stringToSign = `${ALGORITHM}\n${hashedCanonicalRequest}`;
            console.log('stringToSign=========>', stringToSign);
    
            // Step 3: Calculate the signature.
            const signature = hmac256(accessKeySecret, stringToSign);
            console.log('signature=========>', signature);
    
            // Step 4: Construct 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}`;
            // Add 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
            };
    
            // Handle 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) {
        // If the value is null, no further processing is needed.
        if (value === null) {
            return;
        }
        if (key === null) {
            key = "";
        }
    
        // If the value is an array, iterate through its elements and process them recursively.
        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 an object, iterate through its key-value pairs and process them recursively.
            Object.entries(value).forEach(([subKey, subValue]) => {
                processObject(map, `${key}.${subKey}`, subValue);
            });
        } else {
            // If the key starts with a period (.), remove it to keep the key path continuous.
            if (key.startsWith('.')) {
                key = key.slice(1);
            }
            map[key] = String(value);
        }
    }
    
    /**
     * This is a signature example. You must replace the sample parameters in the main function with your actual values.
     * ROA APIs and RPC APIs differ only in the logic for constructing the canonicalUri. The rest of the process is the same.
     *
     * Get the request method (method), parameter name (name), parameter type (type), and parameter location (in) from the API metadata, and then encapsulate these parameters in the Request object.
     * 1. If a parameter's location is "in":"query" in the API metadata, pass it by using queryParam. Note: For RPC APIs, you can also pass these parameters in the request body with a Content-Type of application/x-www-form-urlencoded. See Example 3. * 2. If a parameter's location is "in": "body", pass it in the request body with a Content-Type of application/octet-stream or application/json. For RPC APIs, application/json is not recommended. Instead, use the method shown in Example 3. * 3. If a parameter's location is "in": "formData", pass it in the request body with a Content-Type of application/x-www-form-urlencoded. */ // Example 1: RPC API request with parameters in "query". const httpMethod = 'POST'; // The request method. Most RPC APIs support both POST and GET. This example uses POST. const canonicalUri = '/'; // An RPC API has no resource path, so a forward slash (/) is used as the canonicalUri. const host = 'ecs.cn-hangzhou.aliyuncs.com'; // endpoint const xAcsAction = 'DescribeInstanceStatus'; // API name const xAcsVersion = '2014-05-26'; // API version const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion); // The request parameters for DescribeInstanceStatus are as follows: signRequest.queryParam = { // In the API metadata, RegionId is a String, located in "query", and is required. RegionId: 'cn-hangzhou', // In the API metadata, InstanceId is an Array, located in "query", and is optional. InstanceId: ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"], } // // Example 2: RPC API request with parameters in "body" (file upload scenario). // 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); // const filePath = 'D:\\test.png'; // const bytes = fs.readFileSync(filePath); // // "in": "body" means the parameter is passed in the request body. // signRequest.body = bytes; // signRequest.headers['content-type'] = 'application/octet-stream'; // // Example 3: RPC API request with parameters in "formData" or "body" (non-file upload scenario). // const httpMethod = 'POST'; // The request method. Most RPC APIs support both POST and GET. This example uses POST. // const canonicalUri = '/'; // An RPC API has no resource path, so a forward slash (/) is used as the canonicalUri. // const host = 'mt.aliyuncs.com'; // endpoint // const xAcsAction = 'TranslateGeneral'; // API name // const xAcsVersion = '2018-10-12'; // API version // const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion); // // The request parameters for TranslateGeneral are as follows: // // In the API metadata, Context is a String, located in "query", and is optional. // signRequest.queryParam["Context"] = "Morning"; // // In the API metadata, parameters such as FormatType, SourceLanguage, and TargetLanguage are 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'; // // Example: ROA API POST request // 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); // // "in": "body" means the parameter is passed in the request 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'; // // Example: ROA API GET request // const httpMethod = 'GET'; // // If the canonicalUri includes a path parameter, you must percent-encode it by using the percentCode() function. // 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); // signRequest.queryParam = { // with_addon_resources: true, // } // // Example: ROA API DELETE request // const httpMethod = 'DELETE'; // // If the canonicalUri includes a path parameter, you must percent-encode it by using the percentCode() function. // 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); getAuthorization(signRequest); // Call the API. callApi(signRequest).then(r => { console.log(r); }).catch(error => { console.error(error); }); 

    PHP

    Nota

    Este exemplo usa o ambiente de execução PHP 7.4.33. Ajuste os parâmetros conforme as necessidades do seu negócio.

    <?php
    
    class SignatureDemo
    {
        // 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 UTC.
            $this->AccessKeyId = getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'); // Gets the RAM user's Access Key ID from an environment variable.
            $this->AccessKeySecret = getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'); // Gets the RAM user's Access Key secret from an environment variable.
            $this->SecurityToken = getenv('ALIBABA_CLOUD_SECURITY_TOKEN');
            $this->ALGORITHM = 'ACS3-HMAC-SHA256'; // Sets the encryption algorithm.
        }
    
        /**
         * This example demonstrates API signing. You must replace the sample parameters in the main method with your values.
         * ROA and RPC APIs differ only in the logic for the canonicalUri value. The rest of the process is similar.
         *
         * Get the request method (methods), request parameter names (name), parameter types (type), and parameter locations (in) from the API metadata, and then encapsulate the parameters in SignatureRequest.
         * 1. If a request parameter's location is defined as "in":"query" in the API metadata, pass it using queryParam. Note: For RPC APIs, you can also pass this type of parameter in the body with a content-type of application/x-www-form-urlencoded. See Example 3.
         * 2. If a request parameter's location is defined as "in": "body" in the API metadata, pass it in the body with a MIME type of application/octet-stream or application/json. For RPC APIs, application/json is not recommended. Use the method in Example 3 instead.
         * 3. If a request parameter's location is defined as "in": "formData" in the API metadata, pass it in the body with a MIME type of application/x-www-form-urlencoded.
         */
        public function main()
        {
            // Example 1: RPC API request with "in": "query" parameters.
            $request = $this->createRequest('POST', '/', 'ecs.cn-hangzhou.aliyuncs.com', 'DescribeInstanceStatus', '2014-05-26');
            // The request parameters for DescribeInstanceStatus are as follows:
            $request['queryParam'] = [
                // In the API metadata, RegionId is a required String in the query.
                'RegionId' => 'cn-hangzhou',
                // In the API metadata, InstanceId is an optional array in the query.
                'InstanceId' => ["i-bp11ht4h2kdXXXXXXXX", "i-bp16maz3h3xgXXXXXXXX", "i-bp10r67hmslXXXXXXXX"]
            ];
    
            // // Example 2: RPC API request with "in": "body" parameters (file upload scenario).
            // $request = $this->createRequest('POST', '/', 'ocr-api.cn-hangzhou.aliyuncs.com', 'RecognizeGeneral', '2021-07-07');
            // // The request parameter is specified as "in": "body" in the API metadata. Pass the parameter in the body.
            // $filePath = 'D:\\test.png';
            // // Use a file resource to pass the binary file.
            // $fileResource = fopen($filePath, 'rb');
            // $request['body'] = stream_get_contents($fileResource); 
            // $request['headers']['content-type'] = 'application/octet-stream'; // Sets Content-Type to application/octet-stream.
            // // Close the file resource.
            // fclose($fileResource);
    
            // // Example 3: RPC API request with "in": "formData" or "in": "body" parameters (non-file-upload scenario).
            // $request = $this->createRequest('POST', '/', 'mt.aliyuncs.com', 'TranslateGeneral', '2018-10-12');
            // // The request parameters for TranslateGeneral are as follows:
            // $request['queryParam'] = [
            //     // In the API metadata, Context is an optional String in the query.
            //     'Context' => 'Morning',
            // ];
            // $formData = [
            //     'FormatType' => 'text',
            //     'SourceLanguage' => 'zh',
            //     'TargetLanguage' => 'en',
            //     'SourceText' => 'Hello',
            //     'Scene' => 'general',
            // ];
            // $str = self::formDataToString($formData);
            // $request['body'] = $str;
            // $request['headers']['content-type'] = 'application/x-www-form-urlencoded';
    
            // // ROA API POST request
            // $request = $this->createRequest('POST', '/clusters', 'cs.cn-beijing.aliyuncs.com', 'CreateCluster', '2015-12-15');
            // $bodyData = [
            //     'name' => 'Test Cluster',
            //     'region_id' => 'cn-beijing',
            //     'cluster_type' => 'ExternalKubernetes',
            //     'vpcid' => 'vpc-2zeou1uod4ylaXXXXXXXX',
            //     'service_cidr' => '10.2.0.0/24',
            //     'security_group_id' => 'sg-2ze1a0rlgeo7XXXXXXXX',
            //     "vswitch_ids" => [
            //         "vsw-2zei30dhfldu8XXXXXXXX"
            //     ]
            // ];
            // $request['body'] = json_encode($bodyData, JSON_UNESCAPED_UNICODE);
            // $request['headers']['content-type'] = 'application/json; charset=utf-8'; 
    
            // // ROA API GET request
            // // If canonicalUri contains a path parameter, encode the path parameter using rawurlencode({path_parameter}).
            // $cluster_id = 'c930976b3b1fc4e02bc09831dXXXXXXXX';
            // $canonicalUri = sprintf("/clusters/%s/resources", rawurlencode($cluster_id));
            // $request = $this->createRequest('GET', $canonicalUri, 'cs.cn-beijing.aliyuncs.com', 'DescribeClusterResources', '2015-12-15');
            // $request['queryParam'] = [
            //     'with_addon_resources' => true,
            // ];
    
            // // ROA API DELETE request
            // $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.
            $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 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); // Disables SSL certificate verification. This is insecure and not recommended for production environments.
                curl_setopt($ch, CURLOPT_URL, $url);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Returns the response as a string instead of outputting it directly.
                curl_setopt($ch, CURLOPT_HTTPHEADER, $this->convertHeadersToArray($request['headers'])); // Adds 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 the request.
                $result = curl_exec($ch);
    
                // Check for errors.
                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)
        {
            // If the value is null, return.
            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

    Nota

    Este exemplo usa o ambiente de execução .NET 8.0.302. Ajuste os parâmetros conforme as necessidades do seu negócio.

    using System.Globalization;
    using System.Net;
    using System.Net.Http.Headers;
    using System.Security.Cryptography;
    using System.Text;
    using System.Web;
    using Newtonsoft.Json;
    
    namespace SignatureDemo
    {
        public class Request
        {
            public string HttpMethod { get; private set; }
            public string CanonicalUri { get; private set; }
            public string Host { get; private set; }
            public string XAcsAction { get; private set; }
            public string XAcsVersion { get; private set; }
            public SortedDictionary<string, object> Headers { get; private set; }
            public byte[]? Body { get; set; }
            public Dictionary<string, object> QueryParam { get; set; }
    
            public Request(string httpMethod, string canonicalUri, string host, string xAcsAction, string xAcsVersion)
            {
                HttpMethod = httpMethod;
                CanonicalUri = canonicalUri;
                Host = host;
                XAcsAction = xAcsAction;
                XAcsVersion = xAcsVersion;
                Headers = [];
                QueryParam = [];
                Body = null;
                InitHeader();
            }
    
            private void InitHeader()
            {
                Headers["host"] = Host;
                Headers["x-acs-action"] = XAcsAction;
                Headers["x-acs-version"] = XAcsVersion;
                DateTime utcNow = DateTime.UtcNow;
                Headers["x-acs-date"] = utcNow.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture);
                Headers["x-acs-signature-nonce"] = Guid.NewGuid().ToString();
            }
        }
    
        public class Program
        {
            private static readonly string AccessKeyId = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID") ?? throw new InvalidOperationException("The ALIBABA_CLOUD_ACCESS_KEY_ID environment variable is not set.");
            private static readonly string AccessKeySecret = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET") ?? throw new InvalidOperationException("The ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable is not set.");
            private static readonly string? SecurityToken = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_SECURITY_TOKEN");
            private const string Algorithm = "ACS3-HMAC-SHA256";
            private const string ContentType = "content-type";
    
            /**
            * This example shows how to create a request signature. You must replace the sample parameters in the main method with your actual values.
            * ROA APIs and RPC APIs differ only in the logic for determining the canonicalUri value. The rest of the process is similar.
            *
            * From the API metadata, get the request method, and the name, type, and location (`in`) of each request parameter. Then, encapsulate this information in the Request object.
            * 1. If a request parameter's location is "in":"query" in the metadata, pass it by using `QueryParam`. Note: For RPC APIs, you can also pass these parameters in the request body with a content-type of application/x-www-form-urlencoded. See Example 3.
            * 2. If a request parameter's location is "in": "body" in the metadata, pass it in the request body as `application/octet-stream` or `application/json`. For RPC APIs, we recommend against using `application/json`; use `application/x-www-form-urlencoded` (as shown in Example 3) instead.
            * 3. If a request parameter's location is "in": "formData" in the metadata, pass it in the request body as `application/x-www-form-urlencoded`.
            */
            public static void Main(string[] args)
            {
                // Example 1: RPC API request with "in":"query" parameters
                string httpMethod = "POST"; // The request method. Most RPC APIs support both POST and GET. This example uses POST.
                string canonicalUri = "/"; // RPC APIs do not have a resource path, so a forward slash (/) is used as the `CanonicalUri`.
                string host = "ecs.cn-hangzhou.aliyuncs.com"; // The cloud service endpoint.
                string xAcsAction = "DescribeInstanceStatus"; // The API operation.
                string xAcsVersion = "2014-05-26"; // The API version.
                var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
                // Request parameters for `DescribeInstanceStatus`:
                // `RegionId` is a required string parameter located in the query.
                request.QueryParam["RegionId"] = "cn-hangzhou"; 
                // `InstanceId` is an optional array parameter located in the query.
                List<string> instanceIds = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"];
                request.QueryParam["InstanceId"] = instanceIds; 
    
                // // Example 2: RPC API request with "in":"body" parameter (file upload)
                // 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);
                // // The request parameter is in "body" in the metadata. Pass the parameter in the request body.
                // request.Body = File.ReadAllBytes(@"D:\test.png");
                // request.Headers["content-type"] = "application/octet-stream";
    
                // // Example 3: RPC API request with "in":"formData" or "in":"body" parameters (not a file upload)
                // 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);
                // // The request parameters for TranslateGeneral are as follows:
                // // In the metadata, Context is a String, located in "query", and is optional.
                // request.QueryParam["Context"] = "Morning"; 
                // // In the metadata, parameters such as FormatType, SourceLanguage, and TargetLanguage are 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";
    
                // // Example: ROA API POST request
                // 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 body to 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";
    
                // // Example: ROA API GET request
                // String httpMethod = "GET";
                // // If canonicalUri contains a path parameter, you must encode the path parameter by using `percentCode({path_parameter})`.
                // String canonicalUri = "/clusters/" + PercentCode("c81d501a467594eab873edbf2XXXXXXXX") + "/resources";
                // String host = "cs.cn-beijing.aliyuncs.com"; 
                // String xAcsAction = "DescribeClusterResources"; 
                // String xAcsVersion = "2015-12-15"; 
                // Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
                // request.QueryParam["with_addon_resources"]=true;
    
                // // Example: ROA API DELETE request
                // String httpMethod = "DELETE";
                // // If canonicalUri contains a path parameter, you must encode the path parameter by using `percentCode({path_parameter})`.
                // String canonicalUri = "/clusters/" + PercentCode("c81d501a467594eab873edbf2XXXXXXXX");
                // String host = "cs.cn-beijing.aliyuncs.com"; 
                // String xAcsAction = "DeleteCluster"; 
                // String xAcsVersion = "2015-12-15"; 
                // Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
    
                GetAuthorization(request);
                // Call the API.
                var result = CallApiAsync(request);
                Console.WriteLine($"result:{result.Result}");
            }
    
            private static async Task<string?> CallApiAsync(Request request)
            {
                try
                {
                    // Declare an HttpClient instance.
                    using var httpClient = new HttpClient();
    
                    // Build the URL.
                    string url = $"https://{request.Host}{request.CanonicalUri}";
                    var uriBuilder = new UriBuilder(url);
                    var query = new List<string>();
    
                    // Add 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,
                    };
    
                    // Set 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 `content-type` header is set with the request body content later.
                        {
                            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 the request.
                    HttpResponseMessage response = await httpClient.SendAsync(requestMessage);
                    // Read the response content.
                    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
                {
                    // Flattens `List` or `Dictionary` parameters in `QueryParam`.
                    request.QueryParam = FlattenDictionary(request.QueryParam);
    
                    // Step 1: Build the canonical 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: Build the 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 signature = HmacSha256(AccessKeySecret, stringToSign);
    
                    // Step 4: Build 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 input string cannot be null or empty.");
                }
                return Uri.EscapeDataString(str).Replace("+", "%20").Replace("*", "%2A").Replace("%7E", "~");
            }
        }
    }

    Rust

    Nota

    Este exemplo usa o ambiente de execução rustc 1.82.0. Ajuste os parâmetros conforme as necessidades do seu negócio.

    Para usar o método de assinatura em Rust, adicione as seguintes dependências ao arquivo 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;
    
    // Generates the x-acs-date timestamp.
    pub fn current_timestamp() -> Result<u64, SystemTimeError> {
        Ok(SystemTime::now()
            .duration_since(SystemTime::UNIX_EPOCH)?
            .as_secs())
    }
    // Percent-encodes a string.
    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) // Returns a Cow<str> that can hold either an owned String or a borrowed &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 } /// Calculates the SHA-256 hash of a message. pub fn sha256_hex(message: &str) -> String { let mut hasher = Sha256::new(); hasher.update(message); format!("{:x}", hasher.finalize()).to_lowercase() } // Computes the HMAC-SHA256 signature. 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()) } // Generates a nonce. 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) } // Builds the canonical query string from sorted and encoded parameters. 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("&") } // Reads the response body into a string. 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)) } // Defines value types for FormData. #[derive(Debug, Clone)] pub enum FormValue { String(String), Vec(Vec<String>), HashMap(HashMap<String, String>), } // Defines an enum for different request body types: JSON, binary, or FormData. pub enum RequestBody { Json(HashMap<String, Value>), // JSON Binary(Vec<u8>), // Binary FormData(HashMap<String, FormValue>), // FormData None, } // Builds and sends a signed API 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> { // Processes the request body according to its type. 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 hashes and prepare values for signing. let hashed_request_payload = if body_content.is_empty() { sha256_hex("") } else { sha256_hex(&body_content) }; // x-acs-date in ISO8601 format. 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: The unique nonce for the request. let signature_nonce = generate_nonce(); println!("Signature Nonce: {}", signature_nonce); // The headers to include in the signature. 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 the canonical headers. 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 canonical request string. let canonical_query_string = build_sored_encoded_query_string(query_params); println!("CanonicalQueryString: {}", canonical_query_string); let canonical_request = format!( "{}\n{}\n{}\n{}\n\n{}\n{}", method.as_str(), canonical_uri, canonical_query_string, sign_header_arr.iter().map(|&header| format!("{}:{}", header, headers[header].to_str().unwrap())).collect::<Vec<_>>().join("\n"), sign_headers, hashed_request_payload ); println!("Canonical Request: {}", canonical_request); // 3. Calculate the SHA-256 hash of the canonical request. let result = sha256_hex(&canonical_request); // 4. Construct the 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 the Authorization header. headers.insert("Authorization", HeaderValue::from_str(&auth_data).unwrap()); // Construct the URL with query 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); } // Send the 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) } /// Sends the HTTP request. async fn send_request( client: &Client, method: Method, url: &str, headers: HeaderMap, query_params: &[(&str, &str)], // Request parameters in the query string. body: &RequestBody, // The request body type. body_content: &str, // The serialized request body. ) -> 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 the 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()); // Move this value. } RequestBody::Json(_) => { // Sets the Content-Type header for non-empty JSON bodies. 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(_) => { // For form-data payloads, set the content type. if !body_content.is_empty() { request_builder = request_builder.header("Content-Type", "application/x-www-form-urlencoded"); request_builder = request_builder.body(body_content.to_string()); } } RequestBody::None => { request_builder = request_builder.body(String::new()); } } // Build the request. let request = request_builder .build() .map_err(|e| format!("build request fail: {}", e))?; // Send the request. let response = client .execute(request) .await .map_err(|e| format!("execute request fail: {}", e))?; // Return the response. Ok(response) } /** * * This is a signature example. You must replace the sample parameters in the main function with your actual values. * <p> * You can get the request method, parameter name, type, and location (in) from the API metadata. * 1. If a request parameter is in the query ("in":"query") in the metadata, pass it via query_params. For RPC APIs, you can also pass this type of parameter in the request body with a Content-Type of application/x-www-form-urlencoded. For more information, see Example 3. * 2. If a request parameter is in the body ("in": "body"), pass it as the request body with a MIME type of application/octet-stream or application/json. For RPC APIs, application/json is not recommended. Use the method in Example 3 instead. * 3. If a request parameter is in form data ("in": "formData"), pass it in the request body with a MIME type of application/x-www-form-urlencoded. */ #[tokio::main] async fn main() { // Create an HTTP client. let client = Client::new(); // Retrieve the AccessKey ID and AccessKey secret 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; // RPC API example 1: Request with "in":"query" parameters (POST) let method = Method::POST; let host = "ecs.cn-hangzhou.aliyuncs.com"; // endpoint for ECS let canonical_uri = "/"; // RPC APIs have no resource path, so use a forward slash (/) as the CanonicalURI. let action = "DescribeInstanceStatus"; let version = "2014-05-26"; 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; // The request body is empty. let body = RequestBody:: None; // RPC API example: Request with complex "in":"query" parameters (POST) // let method = Method::POST; // let host = "tds.cn-shanghai.aliyuncs.com"; // let canonical_uri = "/"; // RPC APIs have no resource path, so use a forward slash (/) as the CanonicalURI. // let action = "AddAssetSelectionCriteria"; // let version = "2018-12-03"; // // Define parameters. // let mut target_op = HashMap::new(); // target_op.insert("Operation", "add"); // target_op.insert("Target", "i-2ze1j7ocdXXXXXXXX"); // // Define the TargetOperationList parameter by passing a map into the collection. // let target_operation_list = vec![target_op]; // // Flatten parameters. // let mut query = flatten_target_ops(target_operation_list, "TargetOperationList"); // // Add other parameters. // query.push(("SelectionKey", "85a561b7-27d5-47ad-a0ec-XXXXXXXX")); // let query_params: &[(&str, &str)] = &query; // let body = RequestBody:: None; // RPC API example 2: Request with "in":"body" parameter for a file upload (POST) // let method = Method::POST; // let host = "ocr-api.cn-hangzhou.aliyuncs.com"; // let canonical_uri = "/"; // let action = "RecognizeGeneral"; // let version = "2021-07-07"; // // An "in":"body" parameter for a binary file upload. // let binary_data = std::fs::read("<FILE_PATH>").expect("Failed to read the file."); // Replace  with the actual file path. // // When the body is a binary type. // let body = RequestBody::Binary(binary_data); // // The query parameters are empty. // let query_params = &[]; // RPC API example 3: Request with "in":"formData" or "in":"body" parameters for a non-file upload (POST) // let method = Method::POST; // let host = "mt.aliyuncs.com"; // let canonical_uri = "/"; // let action = "TranslateGeneral"; // let version = "2018-10-12"; // // Parameters such as FormatType, SourceLanguage, and TargetLanguage are specified as "in":"formData" in the API metadata. // let mut form_data = HashMap::new(); // The body type is FormData(HashMap<String, FormValue>). FormValue supports Vec<String>, HashMap<String, String>, etc. You can add more types to the FormValue enum as needed. // form_data.insert(String::from("FormatType"),FormValue::String(String::from("text"))); // form_data.insert(String::from("SourceLanguage"),FormValue::String(String::from("zh"))); // form_data.insert(String::from("TargetLanguage"),FormValue::String(String::from("en"))); // form_data.insert(String::from("SourceText"),FormValue::String(String::from("Hello"))); // form_data.insert(String::from("Scene"),FormValue::String(String::from("general"))); // let query_params = &[("Context", "morning")]; // let body = RequestBody::FormData(form_data); // ROA API POST request: CreateCluster // // Define API request details. // let method = Method::POST; // let host = "cs.cn-hangzhou.aliyuncs.com"; // let canonical_uri = "/clusters"; // let action = "CreateCluster"; // let version = "2015-12-15"; // // Set the request body parameters. // let mut body_json = HashMap::new(); // The body type is Json(HashMap<String, Value>). Value supports types like String, Number, Boolean, Null, Array, and nested JSON objects. // 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 parameters are empty. // let query_params = &[]; // let body = RequestBody::Json(body_json); // ROA API GET request: DescribeClusterResources // let method = Method::GET; // let host = "cs.cn-hangzhou.aliyuncs.com"; // // Construct the resource path. // let uri = format!("/clusters/{}/resources", percent_code("ce196d21571a64be9XXXXXXXX").as_ref()); // let canonical_uri = uri.as_str(); // The resource path as an &str type. // let action = "DescribeClusterResources"; // let version = "2015-12-15"; // // Set the query parameters. // let query_params = &[("with_addon_resources", if true { "true" } else { "false" })]; // Can be "true" or "false". // // Set the request body to empty. // let body = RequestBody:: None; // ROA API DELETE request: 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(); // The resource path as an &str type. // let action = "DeleteCluster"; // let version = "2015-12-15"; // // The query parameters are empty. // let query_params = &[]; // // The request body is empty. // let body = RequestBody:: None; // RPC API example: SendSms // let method = Method::POST; // let host = "dysmsapi.aliyuncs.com"; // let canonical_uri = "/"; // RPC APIs have no resource path, so use a forward slash (/) as the CanonicalURI. // let action = "SendSms"; // let version = "2017-05-25"; // 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; // // The request body is empty. // let body = RequestBody:: None; // Send the request. match call_api( client.clone(), method, // request method: POST, GET, or DELETE host, canonical_uri, query_params, // query parameters ("in":"query") action, version, body, // request body ("in":"body"), supports JSON, FormData, and binary types 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"

    Linguagem 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>
    
    // Retrieves the Access Key ID and Access Key Secret from environment 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 key-value pair for sorting.
    typedef struct {
        char key[256];
        char value[256];
    } KeyValuePair;
    
    // A comparison function to sort keys in lexicographical order.
    int compare_pairs(const void *a, const void *b) {
        return strcmp(((const KeyValuePair *)a)->key, ((const KeyValuePair *)b)->key);
    }
    
    // URL-encodes a string.
    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\n");
            free(encoded); 
            return NULL;
        }
        char* ptr = encoded;
        for (size_t i = 0; i < len; i++) {
            unsigned char c = (unsigned char)str[i];
            if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
                *ptr++ = c;
            } else {
                ptr += sprintf(ptr, "%%%02X", c);
            }
        }
        *ptr = '\0'; 
        char* finalEncoded = malloc(strlen(encoded) + 1);
        if (finalEncoded) {
            char* fptr = finalEncoded;
            for (size_t j = 0; j < strlen(encoded); j++) {
                if (encoded[j] == '+') {
                    strcpy(fptr, "%20");
                    fptr += 3; 
                } else if (encoded[j] == '*') {
                    strcpy(fptr, "%2A");
                    fptr += 3;
                } else if (encoded[j] == '~') {
                    *fptr++ = '~';
                } else {
                    *fptr++ = encoded[j];
                }
            }
            *fptr = '\0'; 
        }
    
        free(encoded); 
        return finalEncoded;
    }
    
    /**
     * @brief URL-encodes and sorts query parameters to generate a canonicalized query string.
     * @param query_params The original query parameter string, for example, "key1=value1&key2=value2".
     * @return char* A pointer to the canonicalized query string. The caller must free the allocated memory.
     */
    char* generate_sorted_encoded_query(const char* query_params) {
        if (query_params == NULL || strlen(query_params) == 0) {
            return strdup(""); // If parameters are empty, return an empty string.
        }
    
        KeyValuePair pairs[100]; // Supports a maximum of 100 key-value pairs.
        int pair_count = 0;
    
        char* copy = strdup(query_params);
        if (!copy) {
            fprintf(stderr, "Failed to allocate memory\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 parameters.
        char* query_sorted = malloc(BUFFER_SIZE);
        if (!query_sorted) {
            fprintf(stderr, "Failed to allocate memory\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;
    }
    
    // Calculates the HMAC-SHA256 hash.
    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';
    }
    // Calculates the SHA-256 hash.
    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';
    }
    // Generates the x-acs-signature-nonce.
    void generate_uuid(char *uuid, size_t size) {
        if (size < 37) { 
            fprintf(stderr, "Buffer size too small for UUID\n");
            return;
        }
        unsigned char random_bytes[16];
        RAND_bytes(random_bytes, sizeof(random_bytes));
        random_bytes[6] &= 0x0f; // Keep the high 4 bits.
        random_bytes[6] |= 0x40; // Set the version to 4.
        random_bytes[8] &= 0x3f; // Keep the high 2 bits.
        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]);
    }
    // Reads a 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; // Read failed.
        }
        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; // Read failed.
        }
        fread(*buffer, 1, file_size, file);
        fclose(file);
        return file_size; // Returns the number of bytes read.
    }
    // Calculates 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 x-acs-signature-nonce, x-acs-date, x-acs-content-sha256, and the string-to-sign.
        generate_uuid(uuid, 37);
        // The format of x-acs-date is yyyy-MM-ddTHH:mm:ssZ, 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);
        // 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 the canonical request.
        // Sort and encode the query parameters.
        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 SHA-256 hash of the canonical request.
        char hashed_canonical_request[SHA256_DIGEST_LENGTH * 2 + 1];
        sha256_hex(canonical_request, hashed_canonical_request);
        printf("hashedCanonicalRequest: %s\n", hashed_canonical_request);
        // 4. Construct the 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 the 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;
    }
    // Sends the API 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) {
        // Variables for the signature calculation.
        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 and get the canonicalized query string.
        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. Construct the request URL. 
        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);
        // Free the memory allocated for the query string.
        if (signed_query_params) {
            free(signed_query_params);
        }
    
        // 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);
        // Other cURL settings: disable SSL verification 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 the 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);
    }
    /**
    *
         * This is a signature calculation example. You must replace the sample parameters in the main function with your actual values.
         * <p>
         * Refer to the API metadata for the request method (methods), parameter name (name), parameter type (type), and parameter location (in).
         * 1. If a request parameter is specified as "in":"query" in the API metadata, pass it via the `query_params` string. Note: For RPC APIs, this type of parameter can also be passed in the request body with a `Content-Type` of `application/x-www-form-urlencoded`. See the "formData" example for details.
         * 2. If a request parameter is specified as "in": "body", pass it in the request body with a MIME type of `application/octet-stream` or `application/json`. For RPC APIs, `application/json` is not recommended. Use the method shown in the "formData" example instead.
         * 3. If a request parameter is specified as "in": "formData", pass it in the request body with a MIME type of `application/x-www-form-urlencoded`.
    */
    int main() {
        // Set the console output encoding to UTF-8.
        SetConsoleOutputCP(CP_UTF8);
        srand((unsigned int)time(NULL));
    
        /**
          * RPC API request example: "in":"query" with complex query parameters.
        */   
        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";
    
        // Define the SelectionKey string parameter.
        const char *selection_key = "85a561b7-27d5-47ad-a0ec-XXXXXXXX";
        // Define the TargetOperationList parameter, an extensible list of target objects.
        struct {
            const char *operation;
            const char *target;
        } targetOperation_list[] = {
            {"add", "i-2ze1j7ocdg9XXXXXXXX"},
            // You can add more entries.
            // {"add", "i-abc123xyzXXXXX"},
        };
    
        int count = sizeof(targetOperation_list) / sizeof(targetOperation_list[0]);
        KeyValuePair pairs[100]; // Store the original keys and values.
        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 encoding are performed in the get_authorization() function.
        qsort(pairs, pair_count, sizeof(KeyValuePair), compare_pairs);
    
        // Construct the original (unencoded) query string.
        char query_params[BUFFER_SIZE] = {0};
        for (int i = 0; i < pair_count; ++i) {
          if (i == 0) {
            snprintf(query_params, sizeof(query_params), "%s=%s", pairs[i].key, pairs[i].value);
         } else {
            char temp[512];
            snprintf(temp, sizeof(temp), "&%s=%s", pairs[i].key, pairs[i].value);
            strncat(query_params, temp, sizeof(query_params) - strlen(query_params) - 1);
        }
    }
        const char *body = ""; // The request body is empty.
        const char *content_type = "application/json; charset=utf-8";
        call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
    
        /**
          * RPC API request example: request parameter "in":"query".
        */
        // Define the API request parameters.
        // 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";
        // // Define the InstanceId parameter, an optional 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]);
        // // Set the query parameters. The RegionId parameter is required.
        // char query_params[BUFFER_SIZE];
        // snprintf(query_params, sizeof(query_params),
        //          "%s&RegionId=cn-hangzhou", InstanceId);
        // const char *body = "";
        // const char *content_type = "application/json; charset=utf-8";
        // call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
    
          /**
            * RPC API request example: request parameter "in":"body" (file upload scenario).
          */
        // Declare a pointer to store the file's content.
        // char *body = NULL;
        // size_t body_length = read_file("<YOUR_FILE_PATH>", &body);
        // if (body_length > 0) {
        //   const char *http_method = "POST";
        //   const char *canonical_uri = "/";
        //   const char *host = "ocr-api.cn-hangzhou.aliyuncs.com";
        //   const char *x_acs_action = "RecognizeGeneral";
        //   const char *x_acs_version = "2021-07-07";
        //   const char *query_params = "";
        //   const char *content_type = "application/octet-stream";
        //   call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, body_length);
        //   free(body);
        // } else {
        //   fprintf(stderr, "File read error\n");
        // }
    
          /**
           * RPC API request example: request parameter "in": "formData" or "in":"body" (non-file upload scenario).
           */
        // const char *http_method = "POST";
        // const char *canonical_uri = "/";
        // const char *host = "mt.aliyuncs.com";
        // const char *x_acs_action = "TranslateGeneral";
        // const char *x_acs_version = "2018-10-12";
        // char query_params[BUFFER_SIZE];
        // snprintf(query_params, sizeof(query_params), "Context=%s", "Morning");
        // const char *format_type = "text";
        // const char *source_language = "zh";
        // const char *target_language = "en";
        // const char *source_text = "Hello";
        // const char *scene = "general";
        // char body[BUFFER_SIZE];
        // snprintf(body, sizeof(body),
        // "FormatType=%s&SourceLanguage=%s&TargetLanguage=%s&SourceText=%s&Scene=%s",
        // percentEncode(format_type), percentEncode(source_language), percentEncode(target_language),
        // percentEncode(source_text), percentEncode(scene));
        // const char *content_type = "application/x-www-form-urlencoded";
        // printf("formdate_body: %s\n", body);
        // call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
    
       /**
        * RPC API request example: request parameter "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));
    
          /**
            * ROA API POST request with parameter "in": "body".
          */
    //    const char *http_method = "POST";
    //    const char *canonical_uri = "/clusters";
    //    const char *host = "cs.cn-beijing.aliyuncs.com";
    //    const char *x_acs_action = "CreateCluster";
    //    const char *x_acs_version = "2015-12-15";
    //    const char *query_params = "";
    //    char body[BUFFER_SIZE];
    //    snprintf(body, sizeof(body),
    //             "{\"name\":\"%s\",\"region_id\":\"%s\",\"cluster_type\":\"%s\","
    //             "\"vpcid\":\"%s\",\"container_cidr\":\"%s\","
    //             "\"service_cidr\":\"%s\",\"security_group_id\":\"%s\","
    //             "\"vswitch_ids\":[\"%s\"]}",
    //             "Test Cluster", "cn-beijing", "ExternalKubernetes",
    //             "vpc-2zeou1uod4yXXXXXXXX", "10.X.X.X/XX",
    //             "10.X.X.X/XX", "sg-2ze1a0rlgeXXXXXXXX",
    //             "vsw-2zei30dhflXXXXXXXX");
    //    const char *content_type = "application/json; charset=utf-8";
    //    call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
    
          /**
            * ROA API GET request.
          */
    //    const char *http_method = "GET";
    //    char canonical_uri[BUFFER_SIZE];
    //    snprintf(canonical_uri, sizeof(canonical_uri), "/clusters/%s/resources", percentEncode("cd1f5ba0dbfa144XXXXXXXX"));
    //    const char *host = "cs.cn-beijing.aliyuncs.com";
    //    const char *x_acs_action = "DescribeClusterResources";
    //    const char *x_acs_version = "2015-12-15";
    //    const char *query_params = "with_addon_resources=true";
    //    const char *body = "";
    //    const char *content_type = "";
    //    call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
    
          /**
            *  ROA API DELETE request.
          */
    //    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));
    
        // Variables to store the generated values.
        char authorization_header[BUFFER_SIZE];
        char hashed_payload[SHA256_DIGEST_LENGTH * 2 + 1];
        char x_acs_date[64];
        char uuid[37];
        return 0;
    }
    

    Perguntas frequentes

    O que fazer se for retornado o erro "Specified signature does not match our calculation" ou "The request signature does not conform to Aliyun standards"?

    A falha na verificação de assinatura geralmente ocorre devido a erros no processo de cálculo da assinatura.

    Causas comuns:

    • Erros na configuração do AccessKey.

    • Localização incorreta de parâmetros: por exemplo, passar parâmetros de consulta no corpo da requisição.

    • Os parâmetros na string de consulta canônica (CanonicalQueryString) não estão ordenados alfabeticamente.

    • Os cabeçalhos usados para o cálculo da assinatura (SignedHeaders) não estão ordenados alfabeticamente.

    • Caracteres de espaço não foram codificados como %20.

    • Os parâmetros foram codificados em URL mais de uma vez. Durante o cálculo da assinatura, a codificação URL deve ser aplicada apenas uma vez aos parâmetros de caminho e à string de consulta canônica. Por exemplo, se a mensagem de erro exibir vários caracteres %25, isso indica que o próprio caractere % foi codificado incorretamente.

    Solução:

    Nota

    Verifique localmente o resultado do cálculo da sua assinatura usando os Valores fixos de parâmetros.

    1. Compare seu valor de CanonicalRequest com o resultado esperado. Se houver diferença, revise a Etapa 1: Construir uma requisição canônica para identificar a discrepância.

    2. Se o CanonicalRequest corresponder, compare o valor de StringToSign. Uma incompatibilidade indica erro no algoritmo de hash.

    3. Caso tanto CanonicalRequest quanto StringToSign correspondam, o problema provavelmente é um AccessKey secret incorreto ou uma implementação falha do algoritmo de criptografia.

    4. Se o problema persistir, entre em contato com o suporte técnico.

    Como usar o Postman para depuração?

    Para depurar chamadas de API no Postman:

    1. Calcule a assinatura e gere o valor do cabeçalho Authorization usando seu código ou script.

    2. Copie todos os cabeçalhos de requisição do seu CanonicalHeaders (incluindo o cabeçalho Authorization que você acabou de gerar) para a aba Headers no Postman. Exemplo:

      Chave

      Valor de exemplo

      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. Adicione os parâmetros da API ao Postman de acordo com seu tipo.

      Nota

      A ordem dos parâmetros deve ser idêntica à ordem usada quando você calculou a assinatura.

      • Para parâmetros de consulta, adicione-os na aba Params.

      • Para parâmetros de corpo, adicione-os na aba Body.

    Como passar os parâmetros da requisição?

    Nos metadados da OpenAPI, o campo in especifica a localização de cada parâmetro, o que determina como ele é passado na requisição.

    Posição

    Descrição

    content-type

    "in": "query"

    Parâmetros de consulta aparecem na URL após o ponto de interrogação (?). Cada par chave-valor é separado por um e comercial (&).

    Opcional. Se fornecido, o valor deve ser application/json.

    "in": "formData"

    Parâmetros de formulário são enviados no corpo da requisição. Eles devem ser concatenados em uma string com o formato key1=value1&key2=value2&key3=value3. Observação: Para tipos complexos como array ou object, você deve "achatar" o valor em pares chave-valor indexados. Por exemplo, um objeto como {"key":["value1","value2"]} deve ser convertido para {"key.1":"value1","key.2":"value2"}.

    Obrigatório. O valor deve ser application/x-www-form-urlencoded.

    "in": "body"

    Parâmetros de corpo são passados diretamente no corpo da requisição.

    Obrigatório. O valor de Content-Type depende do tipo de conteúdo enviado. Por exemplo:

    • Se o conteúdo da requisição for uma string JSON, defina o valor do cabeçalho Content-Type como application/json.

    • Se o conteúdo da requisição for um fluxo de arquivo binário, defina o valor do cabeçalho Content-Type como application/octet-stream.

    De acordo com os metadados da API, o valor de style é RPC ou ROA.

    RPC ou ROA afeta apenas o valor de CanonicalURI. Quando o parâmetro style está definido como um tipo diferente de RPC ou ROA, o valor de CanonicalURI é determinado pelo parâmetro path nos metadados da API. Se o parâmetro path tiver um valor, CanonicalURI assume o valor de path. Caso o parâmetro path não tenha valor, CanonicalURI é definido como uma barra (/). Por exemplo, nos metadados da API para consultar clusters ACK, o valor do parâmetro path é /api/v1/clusters, e o valor de CanonicalURI também é /api/v1/clusters.

    {
      "code": 0,
      "data": {
        "summary": "Queries the details about Container Service for Kubernetes (A...",
        "path": "/api/v1/clusters",
        "methods": [
          "get"
        ],
        "schemes": [
          "http",
          "https"
        ],
        "security": [
          {
            "AK": []
          }
        ]
      }
    }

    Como passar parâmetros do tipo array ou object?

    Ao passar tipos de dados complexos como array ou object, é necessário convertê-los em pares chave-valor indexados.

    Exemplo 1: O {"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"]} deve ser convertido para:

    {
        "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"
    }

    Exemplo 2: {"ImageId":"win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd","RegionId":"cn-shanghai","Tag":[{"tag1":"value1","tag2":"value2"}]} deve ser convertido para:

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

    Como obter o valor de x-acs-version?

    1. Acesse o Portal do Desenvolvedor OpenAPI da Alibaba Cloud. Selecione o produto de nuvem referente à API que deseja chamar. Este exemplo usa o ECS. Na barra de navegação superior do portal, clique em Select Cloud Product para ir à página inicial do produto e visualizar a versão recomendada da API.

    2. Visualize a versão recomendada da API na página inicial do produto. Por exemplo, a versão recomendada da API para ECS é 2014-05-26.

      Na parte inferior da página, há duas abas: Service Area List e API Overview. Use a lista suspensa de versões da API para alternar para uma versão específica e visualizar seus detalhes.

    É possível alternar entre os métodos GET e POST nas chamadas de API?

    • Na maioria dos casos, use o método POST ou GET para chamar uma operação de API no estilo RPC.

    • No entanto, apenas um método específico pode ser usado para chamar uma operação de API no estilo ROA.

    Verifique os métodos suportados nos metadados da API.

    O que fazer se for retornado o erro "You are not authorized to do this operation."?

    Causa: O par de AccessKeys do usuário RAM não possui as permissões necessárias.

    Solução: O que fazer se for retornada a mensagem de erro "code 403. You are not authorized to do this operation. Action: xxxx"?

    Como obter um par de AccessKeys?

    Um par de AccessKeys é uma credencial de acesso permanente composta por um AccessKey ID e um AccessKey secret. A Alibaba Cloud usa pares de AccessKeys para autenticar chamadores de API. Criar um AccessKey para um usuário RAM.

    Fale conosco

    Para dúvidas relacionadas a assinaturas, entre no grupo do DingTalk (ID: 147535001692).

    Nota

    Não entre neste grupo para outras questões.