Tous les produits
Search
Centre de documentation

Alibaba Cloud SDK:Syntaxe de requête et méthode de signature V2 pour les API RPC

Dernière mise à jour :Aug 11, 2026

Envoyez des requêtes HTTP pour appeler des opérations d'API RPC Alibaba Cloud en calculant une signature V2.

Important

La syntaxe de requête et la méthode de signature V2 sont obsolètes. Utilisez la syntaxe de requête et méthode de signature V3.

Syntaxe de requête HTTP

Une requête d'API RPC Alibaba Cloud comprend les éléments suivants :

Composant

Obligatoire

Description

Exemple

Protocole

Oui

Protocole de la requête. Consultez les protocoles pris en charge dans les métadonnées OpenAPI. Si HTTP et HTTPS sont tous deux disponibles, privilégiez HTTPS.

https://

Endpoint

Oui

Endpoint du service. Reportez-vous à la référence d'API de chaque service pour obtenir les endpoints spécifiques à chaque région.

ecs.cn-hangzhou.aliyuncs.com

Paramètres de requête communs

Oui

Paramètres obligatoires dans toutes les requêtes d'API Alibaba Cloud. Pour plus d'informations, consultez la section Paramètres de requête communs de cette rubrique.

Action

Paramètres de requête spécifiques à l'opération

Non

Paramètres propres à l'opération d'API appelée. Consultez ces paramètres dans les métadonnées OpenAPI ou sur le OpenAPI Explorer.

RegionId

HTTPMethod

Oui

Méthode de requête. Les méthodes prises en charge par l'API figurent dans les métadonnées OpenAPI.

GET

Paramètres de requête communs

Chaque requête d'API doit inclure les paramètres suivants :

Paramètre

Type

Obligatoire

Description

Exemple

Action

String

Oui

Opération d'API à appeler. Retrouvez les opérations disponibles sur le OpenAPI Explorer.

CreateInstance

Version

String

Oui

Version de l'API. Vérifiez la version correspondant au service cible sur le OpenAPI Explorer. Par exemple, la version de l'API Short Message Service (SMS) est 2017-05-25.

2014-05-26

Format

String

Non

Format de réponse. Valeurs valides : JSON, XML. Valeur par défaut : XML.

JSON

AccessKeyId

String

Oui

Votre AccessKey ID Alibaba Cloud. Consultez-le dans la console Resource Access Management (RAM). Pour plus d'informations sur la création d'une paire d'AccessKey, reportez-vous à Créer une AccessKey.

yourAccessKeyId

SignatureNonce

String

Oui

Nombre aléatoire unique destiné à prévenir les attaques par rejeu. Utilisez une valeur différente pour chaque requête. Aucune limite de longueur n'est imposée.

15215528852396

Timestamp

String

Oui

Horodatage de la requête au format ISO 8601 (aaaa-MM-jjTHH:mm:ssZ). Valable pendant 31 minutes. Exemple : 2018-01-01T12:00:00Z correspond au 1er janvier 2018 à 20:00:00 (UTC+8).

2018-01-01T12:00:00Z

SignatureMethod

String

Oui

Algorithme de signature. Définissez cette valeur sur HMAC-SHA1.

HMAC-SHA1

SignatureVersion

String

Oui

Version de l'algorithme de signature. Définissez cette valeur sur 1.0.

1.0

Signature

String

Oui

Signature de la requête calculée selon la section Signatures ci-dessous.

Pc5WB8gokVn0xfeu%2FZV%2BiNM1dgI%3D

Transmission des paramètres

Dans les métadonnées OpenAPI, le champ in indique comment transmettre chaque paramètre dans la requête.

Position

Description

content-type

"in": "query"

Les paramètres de requête apparaissent dans l'URL après le caractère ?. Chaque paire clé-valeur est séparée par le caractère &.

Facultatif. Si ce paramètre est fourni, la valeur doit être application/json.

"in": "formData"

Les paramètres de formulaire sont envoyés dans le corps de la requête sous la forme key1=value1&key2=value2&key3=value3. Pour les types complexes tels que array ou object, aplatissez la value en paires clé-valeur indexées. Par exemple, un object tel que {"key":["value1","value2"]} devient {"key.1":"value1","key.2":"value2"}.

Obligatoire. La valeur doit être application/x-www-form-urlencoded.

"in": "body"

Les paramètres du corps sont transmis directement dans le corps de la requête.

Obligatoire. La valeur de Content-Type dépend du type de contenu envoyé. Par exemple :

  • Si le contenu de la requête est une chaîne JSON, définissez la valeur de l'en-tête Content-Type sur application/json.

  • Si le contenu de la requête est un flux de fichier binaire, définissez la valeur de l'en-tête Content-Type sur application/octet-stream.

Remarque

L'ordre des paramètres dans une chaîne JSON n'affecte pas le calcul de la signature.

Méthode de signature

Toutes les requêtes d'API doivent être signées. Alibaba Cloud vérifie l'identité de l'appelant à l'aide de la signature de la requête.

Étape 1 : Construire une chaîne de requête canonique

1. Triez tous les paramètres par ordre alphabétique selon leur clé (à l'exclusion de Signature), en combinant les paramètres de requête communs et les paramètres spécifiques à l'opération. Pseudo-code :

// Concatenate the common request parameters and operation-specific parameters in alphabetical order of parameter keys.
params = merged(publicParams,apiReuqestParams)
sortParams = sorted(params.keys())

2. Encodez les clés et valeurs de sortParams en UTF-8 conformément à la norme RFC 3986, puis associez chaque paire avec le caractère =.

Règles d'encodage :

  • Les lettres majuscules, minuscules, chiffres, tirets (-), traits de soulignement (_), points (.) et tildes (~) ne nécessitent aucun encodage.

  • Les autres caractères ASCII doivent être encodés au format %XY. XY représente le code ASCII des caractères en notation hexadécimale. Par exemple, les guillemets doubles (") sont encodés sous la forme %22. Le tableau suivant décrit certains caractères spéciaux avant et après l'encodage. Prêtez une attention particulière à ces caractères.

    Avant l'encodage

    Après l'encodage

    Espaces ()

    %20

    Astérisques (*)

    %2A

    %7E

    Tildes (~)

Pseudo-code :

encodeURIComponentParam = encodeURIComponent(sortParams.key) + "=" + encodeURIComponent(sortParams.value)

3. Assemblez les paires clé=valeur encodées avec le caractère & pour former la canonicalized query string (en respectant le même ordre de tri qu'à l'étape 1). Pseudo-code :

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

Étape 2 : Construire la chaîne à signer et calculer la signature

Construisez la chaîne StringToSign. Pseudo-code :

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.

Étape 3 : Calculer la signature

Calculez la signature à l'aide de la string-to-sign et de l'AccessKey secret conformément à la norme RFC 2104 (HMAC-SHA1). Pseudo-code :

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

Où :

  • Base64() : fonction d'encodage.

  • HMAC_SHA1() : retourne des données binaires brutes (et non une chaîne hexadécimale).

  • UTF_8_Encoding_Of() : encodeur UTF-8.

Étape 4 : Ajouter la signature à la requête

Encodez la signature dans l'URL conformément à la norme RFC 3986 et ajoutez-la en tant que paramètre Signature. Format final de l'URL :

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

Exemples de signature

Fixed parameter values

Cet exemple appelle l'opération DescribeDedicatedHosts d'ECS pour interroger les détails des hôtes dédiés. Utilisez les valeurs d'exemple ci-dessous pour vérifier votre implémentation de signature.

Paramètre

Valeur d'exemple

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

Paramètres de requête spécifiques à l'opération

Paramètre

Valeur d'exemple

RegionId

cn-beijing

Étapes de calcul de la signature :

  1. Construisez une chaîne de requête canonique.

    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. Construisez la 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. Calculez la signature. Dans cet exemple, l'AccessKey secret est testsecret. Signature obtenue :

    9NaGiOspFP5UPcwX8Iwt2YJXXuk=
  4. Envoyez la requête. Obtenez l'URL complète au format [protocol][endpoint]?[common request parameters][operation-specific parameters].

    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

    Envoyez cette requête à l'aide de cURL ou Wget pour appeler l'opération DescribeDedicatedHosts.

Java

Remarque

Cet exemple utilise Java 8. Ajustez les paramètres selon vos besoins.

Ajoutez la dépendance Maven suivante au fichier 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

Remarque

Cet exemple utilise Python 3.12.3. Ajustez les paramètres selon vos besoins.

Pour exécuter l'exemple de code, installez la bibliothèque 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)

Références

Différencier les styles ROA et RPC.