V2版本RPC风格请求体&签名机制
本文将为您介绍如何通过计算签名的方式发起HTTP请求,调用阿里云RPC风格的OpenAPI。
不再推荐使用该签名版本,推荐使用V3版本请求体&签名机制。
HTTP 请求结构
一个完整的阿里云 RPC 请求由以下部分组成:
名称 | 是否必选 | 描述 | 示例值 |
协议 | 是 | 请求协议,您可以在OpenAPI元数据中查看API支持的请求协议。若API同时支持 | https:// |
服务地址 | 是 | 即 Endpoint。您可以查阅不同云产品的服务接入地址文档获取Endpoint。 | ecs.cn-hangzhou.aliyuncs.com |
公共请求参数 | 是 | 阿里云OpenAPI的公共请求参数,更多信息,请参见下文公共请求参数。 | Action |
接口自定义请求参数 | 否 | API的请求参数,您可以在OpenAPI元数据中查看API定义的请求参数,或者在阿里云 OpenAPI 开发者门户查看。 | RegionId |
HTTPMethod | 是 | 请求方式,您可以在OpenAPI元数据中查看API支持的请求方式。 | GET |
公共请求参数
每个OpenAPI请求都需包含以下参数:
名称 | 类型 | 是否必选 | 描述 | 示例值 |
Action | String | 是 | API 的名称。您可以访问阿里云 OpenAPI 开发者门户,搜索您想调用的API 。 | CreateInstance |
Version | String | 是 | API 版本。您可以访问阿里云 OpenAPI 开发者门户,查看云产品的API版本。例如短信服务产品,您可以通过查看云产品主页中看到API 版本为 2017-05-25。 | 2014-05-26 |
Format | String | 否 | 指定接口返回数据格式,可选 JSON 或 XML,默认为 XML。 | JSON |
AccessKeyId | String | 是 | 阿里云访问密钥 ID。您可以在RAM 控制台查看您的 AccessKeyId。如需创建 AccessKey,请参见创建AccessKey。 | yourAccessKeyId |
SignatureNonce | String | 是 | 签名唯一随机数。用于防止网络重放攻击,建议您每一次请求都使用不同的随机数,随机数位数无限制。 | 15215528852396 |
Timestamp | String | 是 | 按照ISO 8601标准表示的UTC时间,格式为yyyy-MM-ddTHH:mm:ssZ,有效期为31分钟,即生成时间戳后需要在31分钟内发起请求。示例: | 2018-01-01T12:00:00Z |
SignatureMethod | String | 是 | 签名方式。目前为固定值 | HMAC-SHA1 |
SignatureVersion | String | 是 | 签名算法版本。目前为固定值 | 1.0 |
Signature | String | 是 | 请求签名,用户请求的身份验证。更多信息,请参见签名机制。 | Pc5WB8gokVn0xfeu%2FZV%2BiNM1dgI%3D |
参数传递介绍
在OpenAPI元数据中,使用字段in
来定义每个参数的位置,参数的位置将决定参数的传递方式。
参数位置 | 说明 | content-type |
"in": "query" | 查询参数,它们出现在请求URL末尾的问号( | 非必传。若传时,值为 |
"in": "formData" | 表单参数,参数需按照 | 必传,值为content-type=application/x-www-form-urlencoded。 |
"in": "body" | 主体参数,通过请求体(body)传递。 | 必传,content-type的值与请求内容类型有关。例如:
|
当请求参数是JSON字符串类型时,JSON字符串中的参数顺序不会影响签名计算。
签名机制
为了确保 API 的安全性,每个请求都需通过签名(Signature)进行身份验证。以下是签名计算的步骤:
步骤一:构造规范化请求字符串
1、将公共请求参数和接口自定义请求参数合并,并将合并后的参数按照参数首字母的字典顺序进行排序,排序时不包括公共请求参数中的Signature
参数。 伪代码如下:
// 合并公共请求参数和接口自定义参数,并根据key排序
params = merged(publicParams,apiReuqestParams)
sortParams = sorted(params.keys())
2、采用UTF-8字符集,按照RFC3986规范对sortParams
的键和值进行编码,并使用等号(=)将键和值连接。
编码规则:
字符 A~Z、a~z、0~9 以及字符
-
、_
、.
、~
不编码。对其他 ASCII 码字符进行编码。编码格式为%加上16进制的 ASCII 码。例如半角双引号(
"
)将被编码为%22
。需要注意的是,部分特殊字符需要特殊处理,具体如下:编码前
编码后
空格( )
%20
星号(
*
)%2A
%7E
波浪号(
~
)
伪代码如下:
encodeURIComponentParam = encodeURIComponent(sortParams.key) + "=" + encodeURIComponent(sortParams.value)
3、将步骤2的结果通过&
连接,即可得到规范化请求字符串CanonicalizedQueryString
。请注意,参数的排序与第1步保持一致。伪代码如下:
CanonicalizedQueryString = encodeURIComponentParam1 + "&" + encodeURIComponentParam2 + ... + encodeURIComponentParamN
步骤二:构造签名字符串
构造待签名字符串 stringToSign
。该字符串构造规则的伪代码如下:
stringToSign =
HTTPMethod + "&" + // HTTPMethod:发送请求的 HTTP 方法,例如 GET。
encodeURIComponent("/") + "&" + // encodeURIComponent 为步骤一第2步的编码方法
encodeURIComponent(CanonicalizedQueryString) // CanonicalizedQueryString 为步骤一获取的规范化请求字符串。
步骤三:计算签名
按照RFC2104的定义,通过您传入的 AccessKeyId 对应的密钥 AccessKeySecret,使用 HMAC-SHA1
的签名算法,计算待签名字符串StringToSign
的签名。伪代码如下:
signature = Base64(HMAC_SHA1(AccessKeySecret + "&", UTF_8_Encoding_Of(stringToSign)))
其中
Base64() 为编码计算函数。
HMAC_SHA1() 为 HMAC_SHA1 签名函数,返回值为 HMAC_SHA1 加密后原始字节,而非16进制字符串。
UTF_8_Encoding_Of() 是 UTF-8 字符编码函数。
步骤四:将signature添加到URL
将signature的值按照RFC3986规范编码之后添加到接口URL上。伪代码如下:
https://服务地址/?sortParams.key1=sortParams.value1&sortParams.key2=sortParams.value2&...&sortParams.keyN=sortParams.valueN&Signature=signature
签名示例代码
固定参数示例
本示例以调用 ECS DescribeDedicatedHosts查询一台或多台专有宿主机的详细信息为例,根据假设的参数值,展示了签名机制中每个步骤所产生的正确输出内容。您可以在代码中使用本示例提供的假设参数值进行计算,并通过对比您的输出结果与本示例的内容,以验证签名过程的正确性。
所需参数名称 | 假设的参数值 |
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 |
业务请求参数
所需参数名称 | 假设的参数值 |
RegionId | cn-beijing |
签名流程如下:
构造规范化请求字符串。
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
构造待签名字符串
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
计算签名值。根据
AccessKeySecret=testsecret
计算得到的签名值如下:9NaGiOspFP5UPcwX8Iwt2YJXXuk=
发起请求。根据接口URL组成规则
[协议][服务地址]?[公共参数][业务请求参数]
获取完整的请求URL: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
您可以使用curl或者wget等工具发起HTTP请求调用
DescribeDedicatedHosts
,查询一台或多台专有宿主机的详细信息。
Java示例
示例代码的运行环境是Java 8,您可能需要根据具体情况对代码进行相应的调整。
运行Java示例,需要您在pom.xml中添加以下Maven依赖。
<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 {
// 示例一:API请求参数无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");
/*// 示例二:API请求参数有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", "你好");
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");*/
/*// 示例三:API请求参数有body,body为二进制文件
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");*/
// 计算签名
calculateSignature(signatureRequest);
// 发起请求,验证签名是否正确
callApi(signatureRequest);
}
private static void calculateSignature(SignatureRequest signatureRequest) {
// 将header、queryParam、body合成一个map,用于构造规范化请求字符串
signatureRequest.getAllParams();
// 获取规范化请求字符串
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);
// 构造待签名字符串
String stringToSign = signatureRequest.httpMethod + "&" + percentEncode(signatureRequest.canonicalUri) + "&" + percentEncode(String.valueOf(canonicalQueryString));
System.out.println("stringToSign:" + stringToSign);
// 计算签名
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) {
// 如果值为空,则无需进一步处理
if (value == null) {
return;
}
if (key == null) {
key = "";
}
// 当值为List类型时,遍历List中的每个元素,并递归处理
if (value instanceof List<?>) {
List<?> list = (List<?>) value;
for (int i = 0; i < list.size(); ++i) {
processObject(map, key + "." + (i + 1), list.get(i));
}
} else if (value instanceof Map<?, ?>) {
// 当值为Map类型时,遍历Map中的每个键值对,并递归处理
Map<?, ?> subMap = (Map<?, ?>) value;
for (Map.Entry<?, ?> entry : subMap.entrySet()) {
processObject(map, key + "." + entry.getKey().toString(), entry.getValue());
}
} else {
// 对于以"."开头的键,移除开头的"."以保持键的连续性
if (key.startsWith(".")) {
key = key.substring(1);
}
// 对于byte[]类型的值,将其转换为UTF-8编码的字符串
if (value instanceof byte[]) {
map.put(key, new String((byte[]) value, StandardCharsets.UTF_8));
} else {
// 对于其他类型的值,直接转换为字符串
map.put(key, String.valueOf(value));
}
}
}
public static String generateSignature(String accessSecret, String stringToSign) {
try {
// 创建HMAC-SHA1密钥
SecretKeySpec signingKey = new SecretKeySpec((accessSecret + "&").getBytes(StandardCharsets.UTF_8), "HmacSHA1");
// 获取Mac实例并初始化
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
// 计算HMAC-SHA1签名
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("输入字符串不可为null");
}
try {
return URLEncoder.encode(str, StandardCharsets.UTF_8.name()).replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8编码不被支持", e);
}
}
}
Python示例
示例代码的运行环境是Python 3.12.3,您可能需要根据具体情况对代码进行相应的调整。
运行示例代码需要安装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
# 从环境变量中获取访问密钥
ACCESS_KEY_ID = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_ID")
ACCESS_KEY_SECRET = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
class SignatureRequest:
"""
签名请求类,用于构建和管理RPC API请求
"""
def __init__(self, http_method: str, host: str, action: str, version: str):
"""
初始化签名请求对象
Args:
http_method: HTTP请求方法 (GET/POST等)
host: API服务域名
action: API操作名称
version: API版本号
"""
self.http_method = http_method.upper()
self.host = host
self.action = action
self.version = version
self.canonical_uri = "/" # RPC接口统一使用根路径
self.headers: Dict[str, Any] = OrderedDict() # 请求头参数
self.query_params: Dict[str, Any] = OrderedDict() # 查询参数
self.body: Dict[str, Any] = OrderedDict() # 请求体参数
self.body_byte: bytes = b"" # 请求体字节数据
self.all_params: Dict[str, Any] = OrderedDict() # 所有参数集合
self.set_headers()
def set_headers(self) -> None:
"""
设置RPC请求必需的基础请求头参数
"""
self.headers["AccessKeyId"] = ACCESS_KEY_ID # 访问密钥ID
self.headers["Format"] = "JSON" # 响应格式
self.headers["SignatureMethod"] = "HMAC-SHA1" # 签名算法
self.headers["SignatureVersion"] = "1.0" # 签名版本
self.headers["SignatureNonce"] = "{" + str(uuid.uuid4()) + "}" # 随机防重放字符串
self.headers["Timestamp"] = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ") # 时间戳
self.headers["Action"] = self.action # API名称
self.headers["Version"] = self.version # API版本号
def set_content_type(self, content_type):
self.headers["Content-Type"] = content_type
def get_all_params(self) -> None:
"""
收集并排序所有请求参数
"""
# 合并所有参数:headers、query_params、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)
# 按参数名ASCII码顺序排序
self.all_params = OrderedDict(sorted(self.all_params.items()))
def calculate_signature(signature_request: SignatureRequest) -> None:
"""
计算RPC请求的签名
Args:
signature_request: 签名请求对象
"""
signature_request.get_all_params() # 收集并排序所有参数
# 构建规范查询字符串
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}")
# 构建待签名字符串:HTTP方法 + 规范URI + 规范查询字符串
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}")
# 生成签名
signature = generate_signature(ACCESS_KEY_SECRET, string_to_sign)
signature_request.all_params["Signature"] = signature # 将签名添加到参数中
def form_data_to_string(form_data: Dict[str, Any]) -> str:
"""
将表单数据转换为URL编码字符串
Args:
form_data: 表单数据字典
Returns:
URL编码后的字符串
"""
tile_map: Dict[str, Any] = {}
def process_object(key: str, value: Any) -> None:
"""
递归处理对象,将嵌套结构扁平化
Args:
key: 参数键名
value: 参数值
"""
if value is None:
return
if isinstance(value, list):
# 处理列表类型参数
for i, item in enumerate(value):
process_object(f"{key}.{i + 1}", item)
elif isinstance(value, dict):
# 处理字典类型参数
for k, v in value.items():
process_object(f"{key}.{k}", v)
else:
# 移除开头的点
clean_key = key[1:] if key.startswith(".") else key
# 处理字节数据和普通数据
tile_map[clean_key] = value.decode("utf-8") if isinstance(value, bytes) else str(value)
# 处理所有表单数据
for k, v in form_data.items():
process_object(k, v)
# URL编码并拼接
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:
"""
使用HMAC-SHA1算法生成签名
Args:
access_secret: 访问密钥
string_to_sign: 待签名字符串
Returns:
Base64编码的签名结果
"""
try:
# 签名密钥 = 密钥 + "&"
signing_key = (access_secret + "&").encode("utf-8")
# 使用HMAC-SHA1算法计算签名
signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha1).digest()
# Base64编码签名结果
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:
"""
对字符串进行百分号编码(RFC 3986标准)
Args:
s: 待编码字符串
Returns:
编码后的字符串
"""
if s is None:
raise ValueError("Input string cannot be None")
# 使用UTF-8编码后进行URL编码,保留~字符
encoded = urllib.parse.quote(s.encode("utf-8"), safe=b"~")
# 替换特殊字符编码
return encoded.replace("+", "%20").replace("*", "%2A")
def call_api(signature_request: SignatureRequest) -> None:
"""
发起API请求示例
"""
url = f"https://{signature_request.host}/"
# 构造请求参数
params = {k: str(v) for k, v in signature_request.all_params.items()}
# 准备请求参数
request_kwargs = {
"params": params
}
# 添加请求体数据(如果存在)
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:
# 使用requests.request统一处理不同HTTP方法
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__":
# 示例一:无body的请求,content-type非必传,若传请使用application/json
signature_request = SignatureRequest(
http_method="POST",
host="dysmsapi.aliyuncs.com",
action="SendSms",
version="2017-05-25"
)
# query_params用于配置查询参数
signature_request.query_params["SignName"] = "******"
signature_request.query_params["TemplateCode"] = "SMS_******"
signature_request.query_params["PhoneNumbers"] = "******"
signature_request.query_params["TemplateParam"] = "{'code':'1234'}"
# 示例二:带body的请求,content-type为application/x-www-form-urlencoded,禁止使用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": "你好",
"Scene": "general"
}
signature_request.body = body
signature_request.set_content_type("application/x-www-form-urlencoded")
"""
# 示例三:上传二进制文件流,content-type为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_signature(signature_request)
# 发起请求示例
call_api(signature_request)
相关文档
您可以通过以下文档详细了解两种API风格的区别,具体请参见区分ROA风格和RPC风格。