All Products
Search
Document Center

Alibaba Cloud SDK:Request syntax and signature method V2 for RPC APIs

Last Updated:Jun 02, 2026

Send HTTP requests to call Alibaba Cloud RPC-style API operations by calculating a V2 signature.

Important

The request syntax and signature method V2 are discontinued. Use the request syntax and signature method V3.

HTTP request syntax

An Alibaba Cloud RPC API request contains the following components:

Component

Required

Description

Example

Protocol

Yes

The request protocol. Check supported protocols in the OpenAPI metadata. If both HTTP and HTTPS are supported, use HTTPS.

https://

Endpoint

Yes

The service endpoint. Check per-region endpoints in the API reference for each service.

ecs.cn-hangzhou.aliyuncs.com

Common request 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 request parameters

No

The request parameters that are specific to the API operation. You can view the request parameters in the 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 request parameters

Every API request must include the following parameters:

Parameter

Type

Required

Description

Example

Action

String

Yes

The API operation to call. Find available operations in OpenAPI Explorer.

CreateInstance

Version

String

Yes

The API version. Check the version for your target service in OpenAPI Explorer. For example, the Short Message Service (SMS) API version is 2017-05-25.

2014-05-26

Format

String

No

Response format. Valid values: JSON, XML. Default: XML.

JSON

AccessKeyId

String

Yes

Your Alibaba Cloud AccessKey ID. View it in the Resource Access Management (RAM) console. For more information about how to create an AccessKey pair, see Create an AccessKey.

yourAccessKeyId

SignatureNonce

String

Yes

A unique random number to prevent replay attacks. Use a different value for each request. No length limit.

15215528852396

Timestamp

String

Yes

Request timestamp in ISO 8601 format (yyyy-MM-ddTHH:mm:ssZ). Valid for 31 minutes. Example: 2018-01-01T12:00:00Z represents 20:00:00 on January 1, 2018 (UTC+8).

2018-01-01T12:00:00Z

SignatureMethod

String

Yes

Signature algorithm. Set to HMAC-SHA1.

HMAC-SHA1

SignatureVersion

String

Yes

Signature algorithm version. Set to 1.0.

1.0

Signature

String

Yes

The request signature calculated per the Signatures section below.

Pc5WB8gokVn0xfeu%2FZV%2BiNM1dgI%3D

Parameter passing

In the OpenAPI metadata, the in field specifies how each parameter is passed in the request.

Position

Description

content-type

"in": "query"

Query parameters appear in the URL after ?. Each key-value pair is separated by &.

Optional. If provided, the value should be application/json.

"in": "formData"

Form parameters are sent in the request body as key1=value1&key2=value2&key3=value3. For complex types like array or object, flatten the value into indexed key-value pairs. For example, an object like {"key":["value1","value2"]} becomes {"key.1":"value1","key.2":"value2"}.

Required. The value must be application/x-www-form-urlencoded.

"in": "body"

Body parameters are passed directly in the request body.

Required. The value of Content-Type depends on the type of content being sent. For example:

  • If the request content is a JSON string, set the value of the Content-Type header to application/json.

  • If the request content is a binary file stream, set the value of the Content-Type header to application/octet-stream.

Note

Parameter order in a JSON string does not affect signature calculation.

Signature method

All API requests must be signed. Alibaba Cloud verifies the caller's identity using the request signature.

Step 1: Construct a canonicalized query string

1. Sort all parameters alphabetically by key (excluding Signature), combining common request parameters and operation-specific parameters. 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 sortParams keys and values in UTF-8 per RFC 3986, then join each pair with =.

Encoding rules:

  • Uppercase letters, lowercase letters, digits, hyphens (-), underscores (_), periods (.), and tildes (~) do not need to be encoded.

  • Other ASCII characters must be encoded in the %XY format. XY represents the ASCII code of the characters in hexadecimal notation. For example, double quotation marks (") are encoded as %22. The following table describes some special characters before and after encoding. Pay attention to these special characters.

    Before encoding

    After encoding

    Space characters ()

    %20

    Asterisks (*)

    %2A

    %7E

    Titles (~)

Pseudocode:

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

3. Join encoded key=value pairs with & to form the canonicalized query string (same sort order as step 1). Pseudocode:

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

Step 2: Construct a string-to-sign and calculate the signature string

Construct the StringToSign. Pseudocode:

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 string

Calculate the signature using the string-to-sign and AccessKey secret per RFC 2104 (HMAC-SHA1). Pseudocode:

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

Where:

  • Base64(): encoding function.

  • HMAC_SHA1(): returns raw binary data (not a hex string).

  • UTF_8_Encoding_Of(): UTF-8 encoder.

Step 4: Add the signature string to the request

URL-encode the signature per RFC 3986 and add it as the Signature parameter. Final URL format:

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

Signature examples

Fixed parameter values

This example calls the DescribeDedicatedHosts operation of ECS to query dedicated host details. Use the sample values below to verify your signature implementation.

Parameter

Sample 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

Sample value

RegionId

cn-beijing

Signature calculation steps:

  1. Construct a 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 a 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. Calculate the signature string. In this example, the AccessKey secret is testsecret. Signature string:

    9NaGiOspFP5UPcwX8Iwt2YJXXuk=
  4. Initiate the request. Obtain the complete URL in the format of [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

    Send this request using cURL or Wget to call the DescribeDedicatedHosts operation.

Java

Note

This example uses Java 8. Adjust parameters as needed.

Add the following Maven dependency to 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

Note

This example uses Python 3.12.3. Adjust parameters as needed.

To run the sample code, you must 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 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)

References

For more information about the differences between the two API styles, see Differentiate between ROA and RPC styles.