Todos os produtos
Search
Central de documentação

Alibaba Cloud SDK:Sintaxe de solicitação e método de assinatura V2 para APIs RPC

Última atualização: Jul 03, 2026

Envie solicitações HTTP para chamar operações de API no estilo RPC da Alibaba Cloud calculando uma assinatura V2.

Importante

A sintaxe de solicitação e o método de assinatura V2 foram descontinuados. Use a sintaxe de solicitação e método de assinatura V3.

Sintaxe de solicitação HTTP

Uma solicitação de API RPC da Alibaba Cloud contém os seguintes componentes:

Componente

Obrigatório

Descrição

Exemplo

Protocolo

Sim

Protocolo da solicitação. Verifique os protocolos compatíveis nos metadados do OpenAPI. Se houver suporte tanto para HTTP quanto para HTTPS, use HTTPS.

https://

Endpoint

Sim

Endpoint do serviço. Consulte os endpoints por região na referência da API de cada serviço.

ecs.cn-hangzhou.aliyuncs.com

Parâmetros comuns de solicitação

Sim

Parâmetros comuns obrigatórios em todas as solicitações de API da Alibaba Cloud. Para mais informações, consulte a seção Parâmetros comuns de solicitação deste tópico.

Action

Parâmetros de solicitação específicos da operação

Não

Parâmetros específicos da operação de API. Visualize os parâmetros de solicitação nos metadados do OpenAPI ou no OpenAPI Explorer.

RegionId

HTTPMethod

Sim

Método de solicitação. Visualize os métodos compatíveis com a API nos metadados do OpenAPI.

GET

Parâmetros comuns de solicitação

Toda solicitação de API deve incluir os seguintes parâmetros:

Parâmetro

Tipo

Obrigatório

Descrição

Exemplo

Action

String

Sim

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

CreateInstance

Version

String

Sim

Versão da API. Verifique a versão do serviço de destino no OpenAPI Explorer. Por exemplo, a versão da API do Short Message Service (SMS) é 2017-05-25.

2014-05-26

Format

String

Não

Formato de resposta. Valores válidos: JSON, XML. Padrão: XML.

JSON

AccessKeyId

String

Sim

Seu AccessKey ID da Alibaba Cloud. Visualize-o no console do Resource Access Management (RAM). Para mais informações sobre como criar um par de AccessKey, consulte Criar um AccessKey.

yourAccessKeyId

SignatureNonce

String

Sim

Número aleatório único para prevenir ataques de replay. Use um valor diferente para cada solicitação. Sem limite de comprimento.

15215528852396

Timestamp

String

Sim

Carimbo de data/hora da solicitação no formato ISO 8601 (aaaa-MM-ddTHH:mm:ssZ). Válido por 31 minutos. Exemplo: 2018-01-01T12:00:00Z representa 20:00:00 em 1º de janeiro de 2018 (UTC+8).

2018-01-01T12:00:00Z

SignatureMethod

String

Sim

Algoritmo de assinatura. Defina como HMAC-SHA1.

HMAC-SHA1

SignatureVersion

String

Sim

Versão do algoritmo de assinatura. Defina como 1.0.

1.0

Signature

String

Sim

Assinatura da solicitação calculada conforme a seção Assinaturas abaixo.

Pc5WB8gokVn0xfeu%2FZV%2BiNM1dgI%3D

Passagem de parâmetros

Nos metadados do OpenAPI, o campo in especifica como cada parâmetro é transmitido na solicitação.

Posição

Descrição

content-type

"in": "query"

Os parâmetros de consulta aparecem na URL após ?. Cada par chave-valor é separado por &.

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

"in": "formData"

Os parâmetros de formulário são enviados no corpo da solicitação como key1=value1&key2=value2&key3=value3. Para tipos complexos como array ou object, achate o value em pares chave-valor indexados. Por exemplo, um object como {"key":["value1","value2"]} torna-se {"key.1":"value1","key.2":"value2"}.

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

"in": "body"

Os parâmetros de corpo são transmitidos diretamente no corpo da solicitação.

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

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

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

Nota

A ordem dos parâmetros em uma string JSON não afeta o cálculo da assinatura.

Método de assinatura

Todas as solicitações de API devem ser assinadas. A Alibaba Cloud verifica a identidade do chamador usando a assinatura da solicitação.

Etapa 1: Construir uma string de consulta canônica

  1. Ordene todos os parâmetros alfabeticamente pela chave (excluindo Signature), combinando os parâmetros comuns de solicitação e os parâmetros específicos da operação. Pseudocódigo:

    // Concatenate the common request parameters and operation-specific parameters in alphabetical order of parameter keys.
    params = merged(publicParams,apiReuqestParams)
    sortParams = sorted(params.keys())
  2. Codifique as chaves e valores de sortParams em UTF-8 conforme a RFC 3986 e una cada par com =.

Regras de codificação:

  • Letras maiúsculas, letras minúsculas, dígitos, hífens (-), sublinhados (_), pontos (.) e tis (~) não exigem codificação.

  • Outros caracteres ASCII devem ser codificados no formato %XY. XY representa o 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. Atenção a esses caracteres especiais.

    Antes da codificação

    Após a codificação

    Caracteres de espaço ()

    %20

    Asteriscos (*)

    %2A

    %7E

    Tis (~)

Pseudocódigo:

encodeURIComponentParam = encodeURIComponent(sortParams.key) + "=" + encodeURIComponent(sortParams.value)
  1. Una os pares chave=valor codificados com & para formar a string de consulta canônica (mesma ordem de classificação da etapa 1). Pseudocódigo:

    CanonicalizedQueryString = encodeURIComponentParam1 + "&" + encodeURIComponentParam2 + ... + encodeURIComponentParamN

Etapa 2: Construir uma string para assinar e calcular a string de assinatura

Construa a StringToSign. Pseudocódigo:

stringToSign =
  HTTPMethod + "&" + // HTTPMethod specifies the HTTP method that is used to send a request, such as GET. 
  encodeURIComponent("/") + "&" + // encodeURIComponent specifies the encoding method that is used in the second step of Step 1.
  encodeURIComponent(CanonicalizedQueryString) // CanonicalizedQueryString specifies the canonicalized query string that is obtained in Step 1.

Etapa 3: Calcular a string de assinatura

Calcule a assinatura usando a string-to-sign e o segredo do AccessKey conforme a RFC 2104 (HMAC-SHA1). Pseudocódigo:

signature = Base64(HMAC_SHA1(AccessKeySecret + "&", UTF_8_Encoding_Of(stringToSign)))

Onde:

  • Base64(): função de codificação.

  • HMAC_SHA1(): retorna dados binários brutos (não uma string hexadecimal).

  • UTF_8_Encoding_Of(): codificador UTF-8.

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

Codifique a assinatura na URL conforme a RFC 3986 e adicione-a como o parâmetro Signature. Formato final da URL:

https://endpoint/?sortParams.key1=sortParams.value1&sortParams.key2=sortParams.value2&...&sortParams.keyN=sortParams.valueN&Signature=signature

Exemplos de assinatura

Valores fixos de parâmetros

Este exemplo chama a operação DescribeDedicatedHosts do ECS para consultar detalhes de hosts dedicados. Use os valores de amostra abaixo para verificar sua implementação de assinatura.

Parâmetro

Valor de amostra

Endpoint

ecs.cn-beijing.aliyuncs.com

Action

DescribeDedicatedHosts

Version

2014-05-26

Format

JSON

AccessKeyId

testid

AccessKeySecret

testsecret

SignatureNonce

edb2b34af0af9a6d14deaf7c1a5315eb

Timestamp

2023-03-13T08:34:30Z

Parâmetros de solicitação específicos da operação

Parâmetro

Valor de amostra

RegionId

cn-beijing

Etapas de cálculo da assinatura:

  1. Construa uma string de consulta canônica.

    AccessKeyId=testid&Action=DescribeDedicatedHosts&Format=JSON&RegionId=cn-beijing&SignatureMethod=HMAC-SHA1&SignatureNonce=edb2b34af0af9a6d14deaf7c1a5315eb&SignatureVersion=1.0&Timestamp=2023-03-13T08%3A34%3A30Z&Version=2014-05-26
  2. Construa uma string-to-sign.

    GET&%2F&AccessKeyId%3Dtestid%26Action%3DDescribeDedicatedHosts%26Format%3DJSON%26RegionId%3Dcn-beijing%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3Dedb2b34af0af9a6d14deaf7c1a5315eb%26SignatureVersion%3D1.0%26Timestamp%3D2023-03-13T08%253A34%253A30Z%26Version%3D2014-05-26
  3. Calcule a string de assinatura. Neste exemplo, o segredo do AccessKey é testsecret. String de assinatura:

    9NaGiOspFP5UPcwX8Iwt2YJXXuk=
  4. Inicie a solicitação. Obtenha a URL completa no formato [protocolo][endpoint]?[parâmetros comuns de solicitação][parâmetros específicos da operação].

    https://ecs.cn-beijing.aliyuncs.com/?AccessKeyId=testid&Action=DescribeDedicatedHosts&Format=JSON&Signature=9NaGiOspFP5UPcwX8Iwt2YJXXuk%3D&SignatureMethod=HMAC-SHA1&SignatureNonce=edb2b34af0af9a6d14deaf7c1a5315eb&SignatureVersion=1.0&Timestamp=2023-03-13T08%3A34%3A30Z&Version=2014-05-26&RegionId=cn-beijing

    Envie esta solicitação usando cURL ou Wget para chamar a operação DescribeDedicatedHosts.

Java

Nota

Este exemplo usa Java 8. Ajuste os parâmetros conforme necessário.

Adicione a seguinte dependência Maven ao pom.xml:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;

public class Demo {
    private static final String ACCESS_KEY_ID = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
    private static final String ACCESS_KEY_SECRET = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");

    public static class SignatureRequest {
        public final String httpMethod;
        public final String host;
        public final String action;
        public final String version;
        public final String canonicalUri = "/";
        public TreeMap<String, Object> headers = new TreeMap<>();
        public TreeMap<String, Object> queryParams = new TreeMap<>();
        public TreeMap<String, Object> body = new TreeMap<>();
        public TreeMap<String, Object> allParams = new TreeMap<>();
        public byte[] bodyByte;

        public SignatureRequest(String httpMethod, String host, String action, String version) {
            this.httpMethod = httpMethod;
            this.host = host;
            this.action = action;
            this.version = version;
            setExtendedHeaders();
        }

        public void setExtendedHeaders() {
            headers.put("AccessKeyId", ACCESS_KEY_ID);
            headers.put("Format", "JSON");
            headers.put("SignatureMethod", "HMAC-SHA1");
            headers.put("SignatureVersion", "1.0");
            headers.put("SignatureNonce", UUID.randomUUID().toString());
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
            format.setTimeZone(new SimpleTimeZone(0, "GMT"));
            headers.put("Timestamp", format.format(new Date()));
            headers.put("Action", action);
            headers.put("Version", version);
        }

        public void getAllParams() {
            allParams.putAll(headers);
            if (!queryParams.isEmpty()) {
                allParams.putAll(queryParams);
            }
            if (!body.isEmpty()) {
                allParams.putAll(body);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        // Example 1: Send an API request without a body.
        String httpMethod = "POST";
        String endpoint = "dysmsapi.aliyuncs.com";
        String action = "SendSms";
        String version = "2017-05-25";
        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, endpoint, action, version);
        signatureRequest.queryParams.put("PhoneNumbers", "123XXXXXXXX");
        signatureRequest.queryParams.put("SignName", "XXXXXXX");
        signatureRequest.queryParams.put("TemplateCode", "XXXXXXX");
        signatureRequest.queryParams.put("TemplateParam", "XXXXXXX");

        // Example 2: Send an API request with a body.
        String httpMethod = "POST";
        String endpoint = "mt.aliyuncs.com";
        String action = "TranslateGeneral";
        String version = "2018-10-12";
        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, endpoint, action, version);
        TreeMap<String, Object> body = new TreeMap<>();
        body.put("FormatType", "text");
        body.put("SourceLanguage", "zh");
        body.put("TargetLanguage", "en");
        body.put("SourceText", "Hello");
        body.put("Scene", "general");
        signatureRequest.body = body;
        String formDataToString = formDataToString(body);
        signatureRequest.bodyByte = formDataToString.getBytes(StandardCharsets.UTF_8);
        signatureRequest.headers.put("content-type", "application/x-www-form-urlencoded");*/

        /*// Example 3: Send an API request whose body is a binary file.
        String httpMethod = "POST";
        String endpoint = "ocr-api.cn-hangzhou.aliyuncs.com";
        String action = "RecognizeGeneral";
        String version = "2021-07-07";
        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, endpoint, action, version);
        signatureRequest.bodyByte = Files.readAllBytes(Paths.get("D:\\test.png"));
        signatureRequest.headers.put("content-type", "application/octet-stream");*/

        // Calculate the signature string.
        calculateSignature(signatureRequest);

        // Send a request to test whether the signature string is valid.
        callApi(signatureRequest);
    }

    private static void calculateSignature(SignatureRequest signatureRequest) {
        // Merge header, queryParam, and body into a map that is used to construct a canonicalized query string.
        signatureRequest.getAllParams();

        // Construct a canonicalized query string.
        StringBuilder canonicalQueryString = new StringBuilder();
        signatureRequest.allParams.entrySet().stream().map(entry -> percentEncode(entry.getKey()) + "="
                + percentEncode(String.valueOf(entry.getValue()))).forEachOrdered(queryPart -> {
            if (canonicalQueryString.length() > 0) {
                canonicalQueryString.append("&");
            }
            canonicalQueryString.append(queryPart);
        });
        System.out.println("canonicalQueryString:" + canonicalQueryString);

        // Create a string-to-sign.
        String stringToSign = signatureRequest.httpMethod + "&" + percentEncode(signatureRequest.canonicalUri) + "&" + percentEncode(String.valueOf(canonicalQueryString));
        System.out.println("stringToSign:" + stringToSign);
        // Calculate the signature string.
        String signature = generateSignature(ACCESS_KEY_SECRET, stringToSign);
        System.out.println("signature:" + signature);
        signatureRequest.allParams.put("Signature", signature);
    }

    private static void callApi(SignatureRequest signatureRequest) {
        try {
            String url = String.format("https://%s/", signatureRequest.host);
            URIBuilder uriBuilder = new URIBuilder(url);
            for (Map.Entry<String, Object> entry : signatureRequest.allParams.entrySet()) {
                uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue()));
            }
            HttpUriRequest httpRequest;
            switch (signatureRequest.httpMethod) {
                case "GET":
                    httpRequest = new HttpGet(uriBuilder.build());
                    break;
                case "POST":
                    HttpPost httpPost = new HttpPost(uriBuilder.build());
                    if (signatureRequest.bodyByte != null) {
                        httpPost.setEntity(new ByteArrayEntity(signatureRequest.bodyByte, ContentType.create((String) signatureRequest.headers.get("content-type"))));
                    }
                    httpRequest = httpPost;
                    break;
                default:
                    System.out.println("Unsupported HTTP method: " + signatureRequest.httpMethod);
                    throw new IllegalArgumentException("Unsupported HTTP method");
            }
            try (CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpRequest)) {
                String result = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
                System.out.println(result);
            } catch (IOException e) {
                System.out.println("Failed to send request");
                throw new RuntimeException(e);
            }
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    private static String formDataToString(Map<String, Object> formData) {
        Map<String, Object> tileMap = new HashMap<>();
        processObject(tileMap, "", formData);
        StringBuilder result = new StringBuilder();
        boolean first = true;
        String symbol = "&";
        for (Map.Entry<String, Object> entry : tileMap.entrySet()) {
            String value = String.valueOf(entry.getValue());
            if (value != null && !value.isEmpty()) {
                if (first) {
                    first = false;
                } else {
                    result.append(symbol);
                }
                result.append(percentEncode(entry.getKey()));
                result.append("=");
                result.append(percentEncode(value));
            }
        }

        return result.toString();
    }

    private static void processObject(Map<String, Object> map, String key, Object value) {
        // No further processing is required for a null value.
        if (value == null) {
            return;
        }
        if (key == null) {
            key = "";
        }
        // If the value is of the List type, traverse the list and perform recursion on each element.
        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<?, ?>) {
            // If the value is of the Map type, traverse the map and perform recursion on each key-value pair.
            Map<?, ?> subMap = (Map<?, ?>) value;
            for (Map.Entry<?, ?> entry : subMap.entrySet()) {
                processObject(map, key + "." + entry.getKey().toString(), entry.getValue());
            }
        } else {
            // If a key starts with a period (.), remove the period (.) to maintain the continuity of keys.
            if (key.startsWith(".")) {
                key = key.substring(1);
            }
            // If a value is in the byte[] format, convert the value to a string encoded in UTF-8.
            if (value instanceof byte[]) {
                map.put(key, new String((byte[]) value, StandardCharsets.UTF_8));
            } else {
                // Convert the values of other types to strings.
                map.put(key, String.valueOf(value));
            }
        }
    }

    public static String generateSignature(String accessSecret, String stringToSign) {
        try {
            // Create an HMAC-SHA1 key.
            SecretKeySpec signingKey = new SecretKeySpec((accessSecret + "&").getBytes(StandardCharsets.UTF_8), "HmacSHA1");
            // Create and initialize a Mac instance
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(signingKey);
            // Calculate the signature string by using the HMAC-SHA1 algorithm.
            byte[] rawHmac = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(rawHmac);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            System.out.println("Failed to generate HMAC-SHA1 signature");
            throw new RuntimeException(e);
        }
    }

    public static String percentEncode(String str) {
        if (str == null) {
            throw new IllegalArgumentException("The specified string cannot be null.");
        }
        try {
            return URLEncoder.encode(str, StandardCharsets.UTF_8.name()).replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("UTF-8 encoding is not supported.", e);
        }
    }
}

Python

Nota

Este exemplo usa Python 3.12.3. Ajuste os parâmetros conforme necessário.

Para executar o código de amostra, instale a biblioteca requests:

pip install requests
import base64
import hashlib
import hmac
import os
import urllib.parse
import uuid
from collections import OrderedDict
from datetime import datetime, UTC
from typing import Dict, Any

import requests

# Obtain the AccessKey pair 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")

class SignatureRequest:
    """
    A class to construct and manage RPC API requests.
    """

    def __init__(self, http_method: str, host: str, action: str, version: str):
        """
        Initializes the SignatureRequest object.

        Args:
            http_method: The HTTP request method (such as GET and POST).
            host: The API service endpoint.
            action: The API operation name.
            version: The API version.
        """
        self.http_method = http_method.upper()
        self.host = host
        self.action = action
        self.version = version
        self.canonical_uri = "/" # RPC APIs use the root path.
        self.headers: Dict[str, Any] = OrderedDict() # The request headers.
        self.query_params: Dict[str, Any] = OrderedDict() # The query parameters.
        self.body: Dict[str, Any] = OrderedDict() # The equest body.
        self.body_byte: bytes = b"" # The bytes of the request body.
        self.all_params: Dict[str, Any] = OrderedDict() # A collection of all parameters.
        self.set_headers()

    def set_headers(self) -> None:
        """
        Sets the required common headers for an RPC request.
        """
        "AccessKeyId": "<ak id>", //The AccessKey ID.
        ->format('JSON')  // Specifies the response format.
        self.headers["SignatureMethod"] = "HMAC-SHA1" # The signature algorithm.
        self.headers["SignatureVersion"] = "1.0" # The signature version.
        self.headers["SignatureNonce"] = "{" + str(uuid.uuid4()) + "}" # The random anti-replay string.
        self.headers["Timestamp"] = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ") # The timestamp.
        self.headers["Action"] =self.action# The API name.
        self.headers["Version"] =self.version# The API version number.

    def set_content_type(self, content_type):
        self.headers["Content-Type"] = content_type

    def get_all_params(self) -> None:
        """
        Collect and sort all request parameters.
        """
        # Merge all parameters: headers, query_params, and body.
        self.all_params.update(self.headers)
        if self.query_params:
            self.all_params.update(self.query_params)
        if self.body:
            self.body_byte = form_data_to_string(body).encode("utf-8")
            self.all_params.update(self.body)

        # Sort parameters by key in ASCII order.
        self.all_params = OrderedDict(sorted(self.all_params.items()))

def calculate_signature(signature_request: SignatureRequest) -> None:
    """
    Calculate the signature of an RPC request

    Args:
        signature_request: The SignatureRequest object.
    """
    signature_request.get_all_params() # Collect and sort all request parameters.

    # Construct a canonicalized query string.
    canonical_query_string = "&".join(
        f"{percent_encode(k)}={percent_encode(v)}"
        for k, v in signature_request.all_params.items()
    )
    print(f"canonicalQueryString:{canonical_query_string}")

    # Construct a string-to-sign: HTTP method + canonical URI + canonicalized query string.
    string_to_sign = (
        f"{signature_request.http_method}&"
        f"{percent_encode(signature_request.canonical_uri)}&"
        f"{percent_encode(canonical_query_string)}"
    )
    print(f"stringToSign:{string_to_sign}")

    # Generate the signature.
    signature = generate_signature(ACCESS_KEY_SECRET, string_to_sign)
    signature_request.all_params["Signature"] = signature # Add the signature to the parameter.

def form_data_to_string(form_data: Dict[str, Any]) -> str:
    """
    Convert a form data dictionary to a URL-encoded string.

    Args:
        form_data: The form data dictionary.

    Returns:
        A URL-encoded string.
    """
    tile_map: Dict[str, Any] = {}

    def process_object(key: str, value: Any) -> None:
        """
        Recursively flattens nested structures.

        Args:
            key: The parameter key.
            value: the parameter value
        """
        if value is None:
            return
        if isinstance(value, list):
            # Process list-type parameters.
            for i, item in enumerate(value):
                process_object(f"{key}.{i + 1}", item)
        elif isinstance(value, dict):
            # Process dictionary-type parameters.
            for k, v in value.items():
                process_object(f"{key}.{k}", v)
        else:
            # Remove the leading dot.
            clean_key = key[1:] if key.startswith(".") else key
            # Process byte data and regular data.
            tile_map[clean_key] = value.decode("utf-8") if isinstance(value, bytes) else str(value)

    # Process all form data.
    for k, v in form_data.items():
        process_object(k, v)

    # URL-encode and join.
    encoded_items = [
        f"{percent_encode(k)}={percent_encode(v)}"
        for k, v in tile_map.items() if v
    ]

    return "&".join(encoded_items)

def generate_signature(access_secret: str, string_to_sign: str) -> str:
    """
    Generate a signature using the HMAC-SHA1 algorithm.

    Args:
        access_key_secret: The AccessKey secret.
        string_to_sign: The string to be signed.

    Returns:
        A Base64-encoded signature.
    """
    try:
        # he signing key is the AccessKey secret appended with an ampersand (&).
        signing_key = (access_secret + "&").encode("utf-8")
        # Calculate the signature using the HMAC-SHA1 algorithm.
        signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha1).digest()
        # Base64-encode the signature result.
        return base64.b64encode(signature).decode("utf-8")
    except Exception as e:
        print(f"Failed to generate HMAC-SHA1 signature: {e}")
        raise

def percent_encode(s: str) -> str:
    """
    Percent-encodes a string according to RFC 3986.

    Args:
        s: The string to encode.

    Returns:
        The encoded string.
    """
    if s is None:
        raise ValueError("Input string cannot be None")
    # URL-encode using UTF-8, but keep tildes (~) unencoded.
    encoded = urllib.parse.quote(s.encode("utf-8"), safe=b"~")
    # Replace special character encodings.
    return encoded.replace("+", "%20").replace("*", "%2A")

def call_api(signature_request: SignatureRequest) -> None:
    """
    Send the API request.
    """
    url = f"https://{signature_request.host}/"

    # Construct request parameters.
    params = {k: str(v) for k, v in signature_request.all_params.items()}

    # Prepare request arguments.
    request_kwargs = {
        "params": params
    }

    # Add request body data (if any).
    if signature_request.body_byte:
        request_kwargs["data"] = signature_request.body_byte
        headers = {"Content-Type": signature_request.headers.get("Content-Type")}
        request_kwargs["headers"] = headers

    try:
        # Use requests.request to handle different HTTP methods uniformly.
        response = requests.request(
            method=signature_request.http_method,
            url=url,
            **request_kwargs
        )

        print(f"Request URL: {response.url}")
        print(f"Response: {response.text}")

    except requests.RequestException as e:
        print(f"HTTP request failed: {e}")
        raise
    except Exception as e:
        print(f"Failed to send request: {e}")
        raise

if __name__ == "__main__":
    # Example 1: Request with no body. Content-Type is optional. If passed, use application/json.
    signature_request = SignatureRequest(
        http_method="POST",
        host="dysmsapi.aliyuncs.com",
        action="SendSms",
        version="2017-05-25"
    )
    # query_params are used to configure query parameters.
    signature_request.query_params["SignName"] = "******"
    signature_request.query_params["TemplateCode"] = "SMS_******"
    signature_request.query_params["PhoneNumbers"] = "******"
    signature_request.query_params["TemplateParam"] = "{'code':'1234'}"

    # Example 2: Request with a form data body. Content-Type must be application/x-www-form-urlencoded.
    """
    signature_request = SignatureRequest(
        http_method="POST",
        host="mt.aliyuncs.com",
        action="TranslateGeneral",
        version="2018-10-12"
    )
    body = {
        "FormatType": "text",
        "SourceLanguage": "zh",
        "TargetLanguage": "en",
        "SourceText": "Hello",
        "Scene": "general"
    }
    signature_request.body = body
    signature_request.set_content_type("application/x-www-form-urlencoded")
    """

    Example 3: Upload a binary file stream. Content-Type must be application/octet-stream.
    """
    signature_request = SignatureRequest(
        http_method="POST",
        host="ocr-api.cn-hangzhou.aliyuncs.com",
        action="RecognizeGeneral",
        version="2021-07-07"
    )
    with open("D:\\test.jpeg", "rb") as f:
        signature_request.body_byte = f.read()
    signature_request.set_content_type("application/octet-stream")
    """

    # Calculate the signature.
    calculate_signature(signature_request)

    # Send the API request.
    call_api(signature_request)

Referências

Diferenciar entre estilos ROA e RPC.