1pub const BASE_URL: &str = "aliyuncs.com";
2pub const DEFAULT_REGION: &str = "oss-cn-hangzhou";
3pub const USER_AGENT: &str = "xt oss/0.1";
4pub const DEFAULT_CONTENT_TYPE: &str = "application/octet-stream";
5pub const DEFAULT_CONNECT_TIMEOUT: u64 = 180;
6pub const DEFAULT_TIMEOUT: u64 = 60;
7pub const GMT_DATE_FMT: &str = "%a, %d %b %Y %H:%M:%S GMT";
8pub const XML_CONTENT: &str = r#"<?xml version="1.0" encoding="UTF-8"?>"#;
9
10pub use bytes::{Bytes, BytesMut};
11use std::time::Duration;
12pub mod api;
13pub(super) mod auth;
14pub mod entities;
15pub mod http;
16
17use super::oss::{
18 self,
19 http::header::{AUTHORIZATION, CONTENT_TYPE, DATE},
20};
21use chrono::Utc;
22use reqwest::{header::HeaderMap, Response, Result};
23
24pub struct RequestTask<'a> {
25 request: &'a oss::Request<'a>,
26 url: &'a str,
27 resource: Option<&'a str>,
28 method: http::Method,
29 headers: http::HeaderMap,
30 body: Bytes,
31}
32
33impl<'a> RequestTask<'a> {
34 pub(crate) fn new(request: &'a oss::Request<'a>) -> Self {
35 Self {
36 request,
37 url: Default::default(),
38 resource: None,
39 method: http::Method::GET,
40 headers: http::HeaderMap::new(),
41 body: Bytes::new(),
42 }
43 }
44
45 pub fn with_url(mut self, value: &'a str) -> Self {
46 self.url = value;
47 self
48 }
49
50 pub fn with_resource(mut self, value: &'a str) -> Self {
51 self.resource = Some(value);
52 self
53 }
54
55 pub fn with_headers(mut self, value: http::HeaderMap) -> Self {
56 self.headers = value;
57 self
58 }
59
60 pub fn with_method(mut self, value: http::Method) -> Self {
61 self.method = value;
62 self
63 }
64
65 pub fn with_body(mut self, value: Bytes) -> Self {
66 self.body = value;
67 self
68 }
69
70 pub async fn execute(&self) -> oss::Result<Response> {
71 self.inner_execute(None).await
72 }
73
74 pub async fn execute_timeout(&self, value: u64) -> oss::Result<Response> {
75 self.inner_execute(Some(value)).await
76 }
77
78 fn authorization(&self, headers: &HeaderMap, date: &String) -> String {
79 let access_key_id = self.request.access_key_id.unwrap_or_default();
80 let access_key_secret = self.request.access_key_secret.unwrap_or_default();
81 let sts_token = self.request.sts_token;
82 let resourse = self.resource;
83 auth::SingerV1 {
84 access_key_id,
85 access_key_secret,
86 sts_token,
87 headers: &headers,
88 method: &self.method,
89 date: &date,
90 resourse,
91 }
92 .complute()
93 }
94
95 async fn inner_execute(&self, timeout: Option<u64>) -> oss::Result<Response> {
96 let date = Utc::now().format(oss::GMT_DATE_FMT).to_string();
97 let mut headers = http::HeaderMap::new();
98 headers.insert(DATE, date.parse().unwrap());
99 if let Some(sts_token) = self.request.sts_token {
100 headers.insert("x-oss-security-token", sts_token.parse().unwrap());
101 }
102 headers.extend(self.headers.to_owned());
103 let auth = self.authorization(&headers, &date);
104 headers.insert(AUTHORIZATION, auth.parse().unwrap());
105 let timeout = Duration::from_secs(timeout.unwrap_or(oss::DEFAULT_TIMEOUT));
107 self.request
108 .client
109 .request(self.method.to_owned(), self.url)
110 .headers(headers)
111 .timeout(timeout)
112 .body(self.body.to_owned())
113 .send()
114 .await
115 }
116}
117
118#[derive(Debug, Default, Clone)]
119pub struct Request<'a> {
120 access_key_id: Option<&'a str>,
121 access_key_secret: Option<&'a str>,
122 sts_token: Option<&'a str>,
123 client: reqwest::Client,
124}
125
126impl<'a> Request<'a> {
127 pub fn new() -> Self {
128 let mut headers = http::HeaderMap::new();
129 headers.insert(
130 CONTENT_TYPE,
131 http::HeaderValue::from_static(DEFAULT_CONTENT_TYPE),
132 );
133 let client = reqwest::Client::builder()
134 .default_headers(headers)
135 .user_agent(oss::USER_AGENT)
136 .connect_timeout(Duration::from_secs(DEFAULT_CONNECT_TIMEOUT))
137 .build()
138 .unwrap();
139 Self {
140 client,
141 ..Self::default()
142 }
143 }
144
145 pub fn with_access_key_id(mut self, value: &'a str) -> Self {
146 self.access_key_id = Some(value);
147 self
148 }
149
150 pub fn with_access_key_secret(mut self, value: &'a str) -> Self {
151 self.access_key_secret = Some(value);
152 self
153 }
154
155 pub fn with_sts_token(mut self, value: Option<&'a str>) -> Self {
156 self.sts_token = value;
157 self
158 }
159
160 pub fn task(&self) -> RequestTask<'_> {
161 RequestTask::new(&self)
162 }
163}
164
165#[derive(Debug, Clone, Default, Copy)]
166pub struct Options<'a> {
167 access_key_id: &'a str,
169 access_key_secret: &'a str,
171 sts_token: &'a str,
173 bucket: &'a str,
175 endpoint: &'a str,
177 region: &'a str,
179 internal: bool,
181 cname: bool,
183 secure: bool,
187 timeout: u64,
189}
190
191impl<'a> Options<'a> {
192 pub fn new() -> Self {
193 Self {
194 region: oss::DEFAULT_REGION,
195 internal: false,
196 cname: false,
197 secure: false,
199 timeout: 60u64,
200 ..Self::default()
201 }
202 }
203
204 pub fn with_access_key_id(mut self, value: &'a str) -> Self {
205 self.access_key_id = value;
206 self
207 }
208
209 pub fn with_access_key_secret(mut self, value: &'a str) -> Self {
210 self.access_key_secret = value;
211 self
212 }
213
214 pub fn with_bucket(mut self, value: &'a str) -> Self {
215 self.bucket = value;
216 self
217 }
218
219 pub fn with_region(mut self, value: &'a str) -> Self {
220 self.region = value;
221 self
222 }
223
224 pub fn with_sts_token(mut self, value: &'a str) -> Self {
225 self.sts_token = value;
226 self
227 }
228
229 pub fn with_endpoint(mut self, value: &'a str) -> Self {
230 self.endpoint = if let Some(v) = value.strip_prefix("http://") {
231 v
232 } else if let Some(v) = value.strip_prefix("https://") {
233 v
234 } else {
235 value
236 };
237 self
238 }
239
240 pub fn with_internal(mut self, value: bool) -> Self {
241 self.internal = value;
242 self
243 }
244
245 pub fn with_cname(mut self, value: bool) -> Self {
246 self.cname = value;
247 self
248 }
249
250 pub fn with_secret(mut self, value: bool) -> Self {
256 self.secure = value;
257 self
258 }
259 pub fn with_timeout(mut self, value: u64) -> Self {
260 self.timeout = value;
261 self
262 }
263
264 pub fn root_url(&self) -> String {
265 format!(
266 "{}://{}{}.{}",
267 self.schema(),
268 oss::DEFAULT_REGION,
269 if self.internal == true {
270 "-internal"
271 } else {
272 ""
273 },
274 oss::BASE_URL
275 )
276 }
277
278 pub fn base_url(&self) -> String {
279 if self.internal == true {
280 format!("{}://{}.{}", self.schema(), self.bucket, self.host())
281 } else if self.cname == true {
282 format!("{}://{}", self.schema(), self.host())
283 } else {
284 if self.bucket.is_empty() {
285 panic!("Bucket parameter must be provided.");
286 }
287 format!("{}://{}.{}", self.schema(), self.bucket, self.host())
288 }
289 }
290
291 pub fn object_url(&self, object: &'a str) -> String {
292 format!("{}/{}", self.base_url(), object)
293 }
294
295 fn schema(&self) -> String {
296 match self.secure {
297 true => "https".to_string(),
298 false => "http".to_string(),
299 }
300 }
301
302 fn host(&self) -> String {
306 if self.internal == true {
307 format!(
308 "{}{}.{}",
309 self.region,
310 if self.internal { "-internal" } else { "" },
311 oss::BASE_URL
312 )
313 } else if self.cname == true {
314 if self.endpoint.is_empty() {
315 panic!("Endpoint parameter must be provided.");
316 }
317 self.endpoint.to_string()
318 } else {
319 format!("{}.{}", self.region, oss::BASE_URL)
320 }
321 }
322
323 pub fn client(self) -> oss::Client<'a> {
324 oss::Client::new(self)
325 }
326}
327
328#[derive(Debug, Default, Clone)]
329pub struct Client<'a> {
330 options: Options<'a>,
331 request: Request<'a>,
332}
333
334impl<'a> Client<'a> {
335 pub fn new(options: Options<'a>) -> Self {
336 let request = self::Request::new()
337 .with_access_key_id(options.access_key_id)
338 .with_access_key_secret(options.access_key_secret)
339 .with_sts_token((!options.sts_token.is_empty()).then_some(options.sts_token));
340 Self { options, request }
341 }
342
343 pub fn options(&self) -> &Options {
344 &self.options
345 }
346
347 pub fn region(&self) -> &'a str {
348 self.options.region
349 }
350
351 pub fn bucket(&self) -> &'a str {
352 self.options.bucket
353 }
354
355 pub fn root_url(&self) -> String {
356 self.options.root_url()
357 }
358
359 pub fn base_url(&self) -> String {
360 self.options.base_url()
361 }
362
363 pub fn object_url(&self, object: &'a str) -> String {
364 self.options.object_url(object)
365 }
366
367 pub fn timeout(&self) -> u64 {
368 self.options.timeout
369 }
370}
371
372#[cfg(test)]
373pub mod tests {
374 use crate::oss;
375
376 #[test]
377 fn options_new_normal_1() {
378 let options = oss::Options::new()
379 .with_access_key_id("access_key_id")
380 .with_access_key_secret("access_key_secret")
381 .with_region("oss-cn-shanghai")
382 .with_endpoint("cdn.xuetube.com")
383 .with_bucket("xuetube")
384 .with_cname(true)
385 .with_internal(true)
386 .with_secret(true);
387 assert_eq!(
388 options.root_url(),
389 "https://oss-cn-hangzhou-internal.aliyuncs.com"
390 );
391 assert_eq!(
392 options.base_url(),
393 "https://xuetube.oss-cn-shanghai-internal.aliyuncs.com"
394 );
395 }
396
397 #[test]
398 fn options_new_normal_2() {
399 let options = oss::Options::new()
400 .with_access_key_id("access_key_id")
401 .with_access_key_secret("access_key_secret")
402 .with_region("oss-cn-shanghai")
403 .with_bucket("xtoss-ex")
404 .with_secret(true)
405 .with_internal(false);
406
407 let host = "oss-cn-shanghai.aliyuncs.com";
408 let root_url = "https://oss-cn-hangzhou.aliyuncs.com";
409 let base_url = "https://xtoss-ex.oss-cn-shanghai.aliyuncs.com";
410
411 assert_eq!(options.host(), host);
412 assert_eq!(options.root_url(), root_url);
413 assert_eq!(options.base_url(), base_url);
414 }
415
416 #[test]
417 fn options_new_endpoint() {
418 let options = oss::Options::new()
419 .with_access_key_id("access_key_id")
420 .with_access_key_secret("access_key_secret")
421 .with_bucket("xtoss-ex1")
422 .with_cname(true)
423 .with_endpoint("https://cdn.xuetube.com")
424 .with_internal(false)
425 .with_region("oss-cn-shanghai")
426 .with_secret(true)
427 .with_timeout(60);
429
430 let host = "cdn.xuetube.com";
431 let root_url = "https://oss-cn-hangzhou.aliyuncs.com";
432 let base_url = "https://cdn.xuetube.com";
433
434 assert_eq!(options.host(), host);
435 assert_eq!(options.root_url(), root_url);
436 assert_eq!(options.base_url(), base_url);
437 }
438}