Topik ini menjelaskan cara memanggil operasi OpenAPI Alibaba Cloud bergaya RPC dengan mengirimkan permintaan HTTP yang mencakup tanda tangan yang telah dihitung.
Versi tanda tangan ini sudah tidak digunakan lagi. Kami menyarankan Anda menggunakan mekanisme badan permintaan dan tanda tangan V3.
Struktur permintaan HTTP
Permintaan RPC Alibaba Cloud yang lengkap terdiri dari bagian-bagian berikut:
Nama | Wajib | Deskripsi | Contoh |
Protokol | Ya | Protokol permintaan. Anda dapat melihat protokol yang didukung oleh API di metadata OpenAPI. Jika API mendukung keduanya | https:// |
Titik akhir | Ya | Titik akhir layanan. Anda dapat menemukan titik akhir tersebut dalam dokumen pendaftaran layanan setiap produk Alibaba Cloud. | ecs.cn-hangzhou.aliyuncs.com |
Parameter umum | Ya | Parameter umum yang harus disertakan dalam semua permintaan API Alibaba Cloud. Untuk informasi selengkapnya, lihat bagian Parameter permintaan umum dalam topik ini. | Action |
Parameter spesifik operasi | Tidak | Parameter permintaan yang spesifik untuk operasi API. Anda dapat melihatnya di metadata OpenAPI atau di OpenAPI Explorer. | RegionId |
HTTPMethod | Ya | Metode permintaan. Anda dapat melihat metode permintaan yang didukung oleh API di metadata OpenAPI. | GET |
Parameter umum
Setiap permintaan OpenAPI harus menyertakan parameter berikut:
Nama | Tipe | Wajib | Deskripsi | Contoh |
Action | String | Ya | Operasi yang ingin Anda lakukan. Anda dapat mencari operasi API yang ingin Anda lakukan di OpenAPI Explorer. | CreateInstance |
Version | String | Ya | Versi API. Anda dapat mengunjungi Portal Pengembang OpenAPI Alibaba Cloud untuk menemukan versi API suatu produk cloud. Misalnya, versi API untuk Short Message Service adalah 2017-05-25. | 2014-05-26 |
Format | String | Tidak | Format tanggapan. Nilai yang valid: JSON dan XML. Nilai default: XML. | JSON |
AccessKeyId | String | Ya | ID AccessKey yang diberikan kepada Anda oleh Alibaba Cloud. Anda dapat melihat AccessKeyId Anda di Konsol Resource Access Management (RAM). Untuk informasi selengkapnya tentang cara membuat pasangan AccessKey, lihat Buat pasangan AccessKey. | yourAccessKeyId |
SignatureNonce | String | Ya | Bilangan acak unik untuk tanda tangan. Bilangan ini digunakan untuk mencegah serangan replay. Kami menyarankan agar Anda menggunakan bilangan acak yang berbeda untuk setiap permintaan. Jumlah digit dalam bilangan acak tidak dibatasi. | 15215528852396 |
Timestamp | String | Ya | Tentukan waktu dalam standar ISO 8601 dengan format yyyy-MM-ddTHH:mm:ssZ. Cap waktu berlaku selama 31 menit. Anda harus mengirim permintaan dalam waktu 31 menit setelah cap waktu dihasilkan. Misalnya, | 2018-01-01T12:00:00Z |
SignatureMethod | String | Ya | Metode tanda tangan. Nilainya tetap | HMAC-SHA1 |
SignatureVersion | String | Ya | Versi algoritma tanda tangan. Tetapkan nilainya ke | 1.0 |
Signature | String | Ya | String tanda tangan untuk permintaan saat ini. Untuk informasi selengkapnya, lihat Tanda tangan. | Pc5WB8gokVn0xfeu%2FZV%2BiNM1dgI%3D |
Pengiriman parameter
Dalam metadata OpenAPI, bidang in menentukan lokasi setiap parameter, yang menentukan cara parameter dikirimkan.
Posisi parameter | Deskripsi | content-type |
"in": "query" | Parameter kueri muncul setelah tanda tanya ( | Opsional. Jika ditentukan, nilainya harus |
"in": "formData" | Untuk parameter formulir, Anda harus menggabungkan parameter menjadi string dalam format | Wajib. Nilainya adalah content-type=application/x-www-form-urlencoded. |
"in": "body" | Parameter badan. Parameter ini dikirimkan dalam badan permintaan. | Wajib. Nilai content-type tergantung pada jenis konten permintaan. Contohnya:
|
Jika parameter permintaan berupa string JSON, urutan parameter dalam string JSON tidak memengaruhi perhitungan tanda tangan.
Mekanisme tanda tangan
Untuk memastikan keamanan API, setiap permintaan harus diautentikasi dengan tanda tangan. Langkah-langkah berikut menjelaskan cara menghitung tanda tangan:
Langkah 1: Susun string kueri yang dinormalisasi
1. Gabungkan parameter permintaan umum dan parameter permintaan spesifik operasi secara alfabetis berdasarkan kunci parameter, kecuali parameter permintaan umum Signature. Berikut adalah pseudocode-nya:
// Gabungkan parameter permintaan umum dan parameter spesifik API secara alfabetis berdasarkan kunci parameter.
params = merged(publicParams,apiReuqestParams)
sortParams = sorted(params.keys())2. Enkode kunci dan nilai sortParams dalam UTF-8 sesuai dengan RFC 3986. Lalu, gabungkan setiap kunci terenkode dan nilai terenkodenya dengan tanda sama dengan (=).
Aturan pengenkodan:
Huruf kapital, huruf kecil, angka, serta karakter
-,_,., dan~tidak dienkod.Karakter ASCII lainnya harus dienkod dalam format %XY, di mana XY merepresentasikan nilai heksadesimal dari kode ASCII karakter tersebut. Misalnya, tanda kutip ganda (
") dienkod sebagai%22. Tabel berikut mencantumkan enkode untuk beberapa karakter khusus.Sebelum enkode
Sesudah enkode
Spasi ( )
%20Asterisk (
*)%2A%7ETilde (
~)
Pseudocode berikut menunjukkan langkah ini:
encodeURIComponentParam = encodeURIComponent(sortParams.key) + "=" + encodeURIComponent(sortParams.value)3. Gabungkan pasangan kunci-nilai terenkod dari Langkah 2 dengan tanda ampersand (&) untuk membuat CanonicalizedQueryString. Pasangan tersebut harus digabungkan dalam urutan alfabet yang sama seperti yang ditetapkan pada Langkah 1. Berikut adalah pseudocode-nya:
CanonicalizedQueryString = encodeURIComponentParam1 + "&" + encodeURIComponentParam2 + ... + encodeURIComponentParamNLangkah 2: Susun string yang akan ditandatangani
Pseudocode berikut menunjukkan cara menyusun string yang akan ditandatangani stringToSign:
stringToSign =
HTTPMethod + "&" + // HTTPMethod menentukan metode HTTP yang digunakan untuk mengirim permintaan, misalnya GET.
encodeURIComponent("/") + "&" + // encodeURIComponent menentukan metode enkode yang digunakan pada langkah kedua Langkah 1.
encodeURIComponent(CanonicalizedQueryString) // CanonicalizedQueryString menentukan string kueri yang dinormalisasi yang diperoleh pada Langkah 1.Langkah 3: Hitung tanda tangan
Hitung tanda tangan dari string yang akan ditandatangani StringToSign menggunakan algoritma tanda tangan HMAC-SHA1. Kunci untuk fungsi HMAC adalah Rahasia AccessKey Anda yang ditambahkan dengan tanda ampersand (&). Untuk informasi selengkapnya, lihat RFC 2104. Berikut adalah pseudocode-nya:
signature = Base64(HMAC_SHA1(AccessKeySecret + "&", UTF_8_Encoding_Of(stringToSign)))Di mana:
Base64() adalah fungsi untuk enkode Base64.
HMAC_SHA1() adalah fungsi HMAC-SHA1. Fungsi ini mengembalikan byte mentah dari hash HMAC-SHA1, bukan string heksadesimal.
UTF_8_Encoding_Of() adalah fungsi untuk enkode UTF-8.
Langkah 4: Tambahkan tanda tangan ke URL
Enkode nilai tanda tangan yang telah dihitung sesuai dengan RFC 3986 dan tambahkan ke URL permintaan sebagai parameter `Signature`. Berikut adalah pseudocode-nya:
https://service.endpoint/?sortParams.key1=sortParams.value1&sortParams.key2=sortParams.value2&...&sortParams.keyN=sortParams.valueN&Signature=signatureKode contoh tanda tangan
Contoh parameter tetap
Contoh ini memanggil operasi ECS DescribeDedicatedHosts untuk mengkueri detail satu atau beberapa host khusus. Contoh ini menunjukkan keluaran yang diharapkan untuk setiap langkah perhitungan tanda tangan berdasarkan nilai sampel. Anda dapat menggunakan nilai sampel ini untuk menguji implementasi Anda dan memverifikasi bahwa proses tanda tangan Anda benar.
Nama parameter | Nilai parameter asumsi |
Titik akhir | 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 |
Parameter permintaan spesifik operasi
Nama parameter | Nilai parameter asumsi |
RegionId | cn-beijing |
Proses tanda tangan adalah sebagai berikut:
Susun string kueri yang dinormalisasi.
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-26Susun string yang akan ditandatangani
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-26Hitung tanda tangan. Tanda tangan berikut dihitung menggunakan
AccessKeySecret=testsecret:9NaGiOspFP5UPcwX8Iwt2YJXXuk=Susun URL permintaan lengkap. URL memiliki format
[protokol][titik akhir]?[parameter umum][parameter spesifik operasi]: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-beijingAnda dapat menggunakan alat seperti cURL atau Wget untuk mengirim permintaan HTTP guna memanggil operasi
DescribeDedicatedHosts.
Contoh Java
Kode contoh ditulis untuk lingkungan runtime Java 8. Anda mungkin perlu menyesuaikan kode agar sesuai dengan kebutuhan spesifik Anda.
Untuk menjalankan contoh Java, tambahkan dependensi Maven berikut ke file pom.xml Anda.
<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 {
// Contoh 1: Kirim permintaan API tanpa badan.
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");
/*// Contoh 2: Kirim permintaan API dengan badan.
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");*/
/*// Contoh 3: Kirim permintaan API yang badannya berupa file biner.
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");*/
// Hitung string tanda tangan.
calculateSignature(signatureRequest);
// Kirim permintaan untuk menguji apakah string tanda tangan valid.
callApi(signatureRequest);
}
private static void calculateSignature(SignatureRequest signatureRequest) {
// Gabungkan header, queryParam, dan body ke dalam peta yang digunakan untuk menyusun string kueri yang dinormalisasi.
signatureRequest.getAllParams();
// Susun string kueri yang dinormalisasi.
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);
// Buat string yang akan ditandatangani.
String stringToSign = signatureRequest.httpMethod + "&" + percentEncode(signatureRequest.canonicalUri) + "&" + percentEncode(String.valueOf(canonicalQueryString));
System.out.println("stringToSign:" + stringToSign);
// Hitung string tanda tangan.
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("Metode HTTP tidak didukung: " + signatureRequest.httpMethod);
throw new IllegalArgumentException("Metode HTTP tidak didukung");
}
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("Gagal mengirim permintaan");
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) {
// Tidak diperlukan pemrosesan lebih lanjut untuk nilai null.
if (value == null) {
return;
}
if (key == null) {
key = "";
}
// Jika nilai bertipe List, telusuri daftar dan lakukan rekursi pada setiap elemen.
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<?, ?>) {
// Jika nilai bertipe Map, telusuri peta dan lakukan rekursi pada setiap pasangan kunci-nilai.
Map<?, ?> subMap = (Map<?, ?>) value;
for (Map.Entry<?, ?> entry : subMap.entrySet()) {
processObject(map, key + "." + entry.getKey().toString(), entry.getValue());
}
} else {
// Jika kunci diawali dengan titik (.), hapus titik (.) tersebut agar kunci tetap berkesinambungan.
if (key.startsWith(".")) {
key = key.substring(1);
}
// Jika nilai berformat byte[], konversi nilai tersebut menjadi string yang dienkod dalam UTF-8.
if (value instanceof byte[]) {
map.put(key, new String((byte[]) value, StandardCharsets.UTF_8));
} else {
// Konversi nilai tipe lain menjadi string.
map.put(key, String.valueOf(value));
}
}
}
public static String generateSignature(String accessSecret, String stringToSign) {
try {
// Buat kunci HMAC-SHA1.
SecretKeySpec signingKey = new SecretKeySpec((accessSecret + "&").getBytes(StandardCharsets.UTF_8), "HmacSHA1");
// Buat dan inisialisasi instance Mac
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
// Hitung string tanda tangan menggunakan algoritma HMAC-SHA1.
byte[] rawHmac = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(rawHmac);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
System.out.println("Gagal menghasilkan tanda tangan HMAC-SHA1");
throw new RuntimeException(e);
}
}
public static String percentEncode(String str) {
if (str == null) {
throw new IllegalArgumentException("String yang ditentukan tidak boleh null.");
}
try {
return URLEncoder.encode(str, StandardCharsets.UTF_8.name()).replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Enkode UTF-8 tidak didukung.", e);
}
}
}
Contoh Python
Kode contoh ini ditulis untuk Python 3.12.3, dan Anda mungkin perlu menyesuaikan kode agar sesuai dengan lingkungan Anda.
Untuk menjalankan kode contoh, instal library 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
# Dapatkan pasangan AccessKey dari variabel lingkungan.
ACCESS_KEY_ID = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_ID")
ACCESS_KEY_SECRET = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
class SignatureRequest:
"""
Kelas untuk menandatangani permintaan. Kelas ini digunakan untuk membangun dan mengelola permintaan API RPC.
"""
def __init__(self, http_method: str, host: str, action: str, version: str):
"""
Inisialisasi objek permintaan tanda tangan.
Args:
http_method: Metode permintaan HTTP, seperti GET atau POST.
host: Nama domain layanan API.
action: Nama aksi API.
version: Nomor versi API.
"""
self.http_method = http_method.upper()
self.host = host
self.action = action
self.version = version
self.canonical_uri = "/" # API RPC menggunakan path root.
self.headers: Dict[str, Any] = OrderedDict() # Parameter header permintaan.
self.query_params: Dict[str, Any] = OrderedDict() # Parameter kueri.
self.body: Dict[str, Any] = OrderedDict() # Parameter badan permintaan.
self.body_byte: bytes = b"" # Badan permintaan dalam bentuk byte.
self.all_params: Dict[str, Any] = OrderedDict() # Kumpulan semua parameter.
self.set_headers()
def set_headers(self) -> None:
"""
Menetapkan parameter header permintaan dasar yang diperlukan untuk permintaan RPC.
"""
self.headers["AccessKeyId"] = ACCESS_KEY_ID # ID AccessKey.
self.headers["Format"] = "JSON" # Format tanggapan.
self.headers["SignatureMethod"] = "HMAC-SHA1" # Algoritma tanda tangan.
self.headers["SignatureVersion"] = "1.0" # Versi tanda tangan.
self.headers["SignatureNonce"] = "{" + str(uuid.uuid4()) + "}" # String acak untuk anti-replay.
self.headers["Timestamp"] = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ") # Cap waktu.
self.headers["Action"] = self.action # Nama API.
self.headers["Version"] = self.version # Nomor versi API.
def set_content_type(self, content_type):
self.headers["Content-Type"] = content_type
def get_all_params(self) -> None:
"""
Mengumpulkan dan mengurutkan semua parameter permintaan.
"""
# Gabungkan semua parameter: headers, query_params, dan 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)
# Urutkan parameter berdasarkan nama dalam urutan ASCII.
self.all_params = OrderedDict(sorted(self.all_params.items()))
def calculate_signature(signature_request: SignatureRequest) -> None:
"""
Menghitung tanda tangan untuk permintaan RPC.
Args:
signature_request: Objek permintaan tanda tangan.
"""
signature_request.get_all_params() # Kumpulkan dan urutkan semua parameter.
# Bangun string kueri yang dinormalisasi.
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}")
# Bangun string yang akan ditandatangani: metode HTTP + URI kanonis + string kueri kanonis.
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}")
# Hasilkan tanda tangan.
signature = generate_signature(ACCESS_KEY_SECRET, string_to_sign)
signature_request.all_params["Signature"] = signature # Tambahkan tanda tangan ke parameter.
def form_data_to_string(form_data: Dict[str, Any]) -> str:
"""
Mengonversi data formulir menjadi string yang dienkod URL.
Args:
form_data: Kamus data formulir.
Returns:
String yang dienkod URL.
"""
tile_map: Dict[str, Any] = {}
def process_object(key: str, value: Any) -> None:
"""
Memproses objek secara rekursif untuk meratakan struktur bersarangnya.
Args:
key: Kunci parameter.
value: Nilai parameter.
"""
if value is None:
return
if isinstance(value, list):
# Proses parameter bertipe daftar.
for i, item in enumerate(value):
process_object(f"{key}.{i + 1}", item)
elif isinstance(value, dict):
# Proses parameter bertipe kamus.
for k, v in value.items():
process_object(f"{key}.{k}", v)
else:
# Hapus titik awal.
clean_key = key[1:] if key.startswith(".") else key
# Proses data byte dan data biasa.
tile_map[clean_key] = value.decode("utf-8") if isinstance(value, bytes) else str(value)
# Proses semua data formulir.
for k, v in form_data.items():
process_object(k, v)
# Enkode URL dan gabungkan item.
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:
"""
Menghasilkan tanda tangan menggunakan algoritma HMAC-SHA1.
Args:
access_secret: Rahasia AccessKey.
string_to_sign: String yang akan ditandatangani.
Returns:
Tanda tangan yang dienkod Base64.
"""
try:
# Kunci penandatanganan adalah rahasia AccessKey yang ditambahkan dengan tanda ampersand (&).
signing_key = (access_secret + "&").encode("utf-8")
# Hitung tanda tangan menggunakan algoritma HMAC-SHA1.
signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha1).digest()
# Enkode tanda tangan dalam Base64.
return base64.b64encode(signature).decode("utf-8")
except Exception as e:
print(f"Gagal menghasilkan tanda tangan HMAC-SHA1: {e}")
raise
def percent_encode(s: str) -> str:
"""
Melakukan enkode persen pada string berdasarkan RFC 3986.
Args:
s: String yang akan dienkod.
Returns:
String yang telah dienkod.
"""
if s is None:
raise ValueError("String input tidak boleh None")
# Enkode URL string setelah enkode UTF-8. Karakter tilde (~) tidak dienkod.
encoded = urllib.parse.quote(s.encode("utf-8"), safe=b"~")
# Ganti enkode karakter khusus.
return encoded.replace("+", "%20").replace("*", "%2A")
def call_api(signature_request: SignatureRequest) -> None:
"""
Contoh cara memulai permintaan API.
"""
url = f"https://{signature_request.host}/"
# Susun parameter permintaan.
params = {k: str(v) for k, v in signature_request.all_params.items()}
# Siapkan parameter permintaan.
request_kwargs = {
"params": params
}
# Tambahkan data badan permintaan jika ada.
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:
# Gunakan requests.request untuk menangani berbagai metode HTTP.
response = requests.request(
method=signature_request.http_method,
url=url,
**request_kwargs
)
print(f"URL Permintaan: {response.url}")
print(f"Tanggapan: {response.text}")
except requests.RequestException as e:
print(f"Permintaan HTTP gagal: {e}")
raise
except Exception as e:
print(f"Gagal mengirim permintaan: {e}")
raise
if __name__ == "__main__":
# Contoh 1: Permintaan tanpa badan. Header Content-Type opsional. Jika Anda menentukan header ini, tetapkan nilainya ke application/json.
signature_request = SignatureRequest(
http_method="POST",
host="dysmsapi.aliyuncs.com",
action="SendSms",
version="2017-05-25"
)
# Gunakan query_params untuk mengonfigurasi parameter kueri.
signature_request.query_params["SignName"] = "******"
signature_request.query_params["TemplateCode"] = "SMS_******"
signature_request.query_params["PhoneNumbers"] = "******"
signature_request.query_params["TemplateParam"] = "{'code':'1234'}"
# Contoh 2: Permintaan dengan badan. Header Content-Type harus diatur ke application/x-www-form-urlencoded. Jangan atur ke 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")
"""
# Contoh 3: Unggah aliran file biner. Header Content-Type harus diatur ke 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")
"""
# Hitung tanda tangan.
calculate_signature(signature_request)
# Mulai permintaan contoh.
call_api(signature_request)
Referensi
Untuk informasi selengkapnya tentang perbedaan antara API bergaya RPC dan API bergaya ROA, lihat Gaya API.