1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use base64;
use chrono::{Local, Utc};
use crypto::hmac::Hmac;
use crypto::mac::Mac;
use crypto::sha1::Sha1;
use failure::Error;
use reqwest::Client as HttpClient;
use url::form_urlencoded::byte_serialize;
const DEFAULT_PARAM: &[(&str, &str)] = &[
("Format", "JSON"),
("SignatureMethod", "HMAC-SHA1"),
("SignatureVersion", "1.0"),
];
#[derive(Clone, Debug)]
pub struct Client {
access_key_id: String,
access_key_secret: String,
endpoint: String,
version: String,
http: HttpClient,
}
impl Client {
pub fn new(
access_key_id: String,
access_key_secret: String,
endpoint: String,
version: String,
) -> Self {
Client {
access_key_id,
access_key_secret,
endpoint,
version,
http: HttpClient::new(),
}
}
pub fn request(&self, action: &str, queries: &[(&str, &str)]) -> Result<String, Error> {
let nonce = Local::now().timestamp_subsec_nanos().to_string();
let ts = Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string();
let mut params = Vec::from(DEFAULT_PARAM);
params.push(("Action", &action));
params.push(("AccessKeyId", &self.access_key_id));
params.push(("SignatureNonce", &nonce));
params.push(("Timestamp", &ts));
params.push(("Version", &self.version));
params.extend_from_slice(&queries);
params.sort_by_key(|item| item.0);
let params: Vec<String> = params
.into_iter()
.map(|(k, v)| format!("{}={}", Client::url_encode(k), Client::url_encode(v)))
.collect();
let sorted_query_string = params.join("&");
let string_to_sign = format!(
"GET&{}&{}",
Client::url_encode("/"),
Client::url_encode(&sorted_query_string)
);
let sign = Client::sign(&format!("{}&", self.access_key_secret), &string_to_sign);
let signature = Client::url_encode(&sign);
let final_url = format!(
"{}?Signature={}&{}",
self.endpoint, signature, sorted_query_string
);
let response = self.http.get(&final_url).send()?.text()?;
Ok(response)
}
fn url_encode(s: &str) -> String {
let s: String = byte_serialize(s.as_bytes()).collect();
s.replace("+", "%20")
.replace("*", "%2A")
.replace("%7E", "~")
}
fn sign(key: &str, body: &str) -> String {
let mut mac = Hmac::new(Sha1::new(), key.as_bytes());
mac.input(body.as_bytes());
let result = mac.result();
let code = result.code();
base64::encode(code)
}
}