Skip to main content

u_sdk/oss/object/
basic.rs

1//! 关于Object操作/基础操作
2//!
3//! [官方文档](https://help.aliyun.com/zh/oss/developer-reference/basic-operations-1/)
4
5use super::types_rs::*;
6use crate::oss::Client;
7use crate::oss::Error;
8use crate::oss::sign_v4::HTTPVerb;
9use crate::oss::utils::{
10    PresignParams, compute_md5_from_file, generate_presigned_url, get_content_md5,
11    get_request_header, hmac_sha256_bytes, into_request_failed_error,
12    parse_get_object_response_header, parse_xml_response, utc_date_str, utc_date_time_str,
13    validate_object_name,
14};
15use base64::{Engine, engine::general_purpose};
16use bytes::Bytes;
17use reqwest::header::HeaderMap;
18use reqwest::{Body, StatusCode};
19use serde_json::{Map, Value};
20use std::collections::HashMap;
21use std::path::Path;
22use time::OffsetDateTime;
23use time::format_description::well_known::Iso8601;
24use tokio::io::AsyncWriteExt;
25use tokio_stream::{Stream, StreamExt};
26use tokio_util::io::ReaderStream;
27
28impl<'a> PutObject<'a> {
29    /// - `content_type`,不会进行MIME合法性检查
30    /// - `object_name`:遵守OSS的Object[命名规则](https://help.aliyun.com/zh/oss/user-guide/object-naming-conventions)
31    /// - `data`:如果需要创建文件夹,object_name以`/`结尾,`Vec`大小为0即可
32    pub async fn send(
33        &self,
34        object_name: &'a str,
35        object: PutObjectBody<'a>,
36    ) -> Result<PutObjectResponseHeader, Error> {
37        validate_object_name(object_name)?;
38
39        let client = self.client;
40        let request_url = url::Url::parse(&format!(
41            "https://{}.{}/{}", // url不能添加`/`结尾,因为是否有`/`由object_name决定
42            client.bucket, client.endpoint, object_name
43        ))
44        .unwrap();
45
46        let mut req_header_map: HashMap<String, String> =
47            serde_json::from_value(serde_json::to_value(self).unwrap()).unwrap();
48        // 添加api剩下的请求头
49        match &object {
50            PutObjectBody::Bytes(bytes) => {
51                req_header_map.insert("content-md5".to_owned(), get_content_md5(bytes.as_slice()));
52                req_header_map.insert("content-length".to_owned(), bytes.len().to_string());
53            }
54            PutObjectBody::FilePath(path) => {
55                let file_size = std::fs::metadata(path)?.len();
56                req_header_map.insert("content-length".to_owned(), file_size.to_string());
57                let md5_str = compute_md5_from_file(path).await?;
58                req_header_map.insert("content-md5".to_owned(), md5_str);
59            }
60        }
61
62        // 如果有x-meta-*,将其添加到请求头中
63        if !self.custom_metas.is_empty() {
64            let custom_meta_map = self
65                .custom_metas
66                .iter()
67                .map(|(k, v)| (k.clone(), v.clone()))
68                .collect::<HashMap<_, _>>();
69            req_header_map.extend(custom_meta_map);
70        };
71
72        if let Some(oss_callback) = &self.callback {
73            let callback_base64 =
74                general_purpose::STANDARD.encode(serde_json::to_string(oss_callback).unwrap());
75            // println!("callback_base64: {}", &callback_base64);
76            req_header_map.insert("x-oss-callback".to_owned(), callback_base64);
77            if !oss_callback.callback_body.callback_var.is_empty() {
78                let callback_var_map = oss_callback
79                    .callback_body
80                    .callback_var
81                    .iter()
82                    .map(|(_, k, v)| (k, v))
83                    .collect::<HashMap<_, _>>();
84                let callback_var_base64 = general_purpose::STANDARD
85                    .encode(serde_json::to_string(&callback_var_map).unwrap());
86                // println!("callback_var_base64: {}", &callback_var_base64);
87                req_header_map.insert("x-oss-callback-var".to_owned(), callback_var_base64);
88            }
89        }
90
91        let creds = client.credentials_provider.load().await?;
92        // sts token
93        if let Some(token) = &creds.sts_security_token {
94            req_header_map.insert("x-oss-security-token".to_owned(), token.clone());
95        }
96
97        let header_map = get_request_header(
98            &creds.access_key_id,
99            &creds.access_key_secret,
100            req_header_map,
101            &request_url,
102            HTTPVerb::Put,
103            &client.region,
104            Some(&client.bucket),
105        );
106
107        let data = match object {
108            PutObjectBody::Bytes(bytes) => Body::from(bytes),
109            PutObjectBody::FilePath(path) => {
110                let file = tokio::fs::File::open(path).await?;
111                let stream = ReaderStream::new(file);
112                Body::wrap_stream(stream)
113            }
114        };
115
116        let resp = client
117            .http_client
118            .put(request_url)
119            .headers(header_map)
120            .body(data)
121            .send()
122            .await?;
123        // println!("response: {:#?}", resp);
124
125        // 如果上传成功,但是使用callback的时候应用服务器端没有响应导致回调失败,会返回203
126        if resp.status() != StatusCode::OK {
127            return Err(into_request_failed_error(resp).await);
128        }
129
130        let header = resp.headers();
131        let content_md5 = header
132            .get("Content-MD5")
133            .unwrap()
134            .to_str()
135            .unwrap()
136            .to_owned();
137        let x_oss_hash_crc64ecma = header
138            .get("x-oss-hash-crc64ecma")
139            .unwrap()
140            .to_str()
141            .unwrap()
142            .to_owned();
143        let x_oss_version_id = header
144            .get("x-oss-version-id")
145            .map(|v| v.to_str().unwrap().to_owned());
146
147        Ok(PutObjectResponseHeader {
148            content_md5,
149            x_oss_hash_crc64ecma,
150            x_oss_version_id,
151        })
152    }
153
154    /// 生成用于上传的预签名URL(Presigned URL),生成的url使用PUT方法上传文件
155    ///
156    /// [在URL中包含签名](https://help.aliyun.com/zh/oss/developer-reference/add-signatures-to-urls)
157    ///
158    /// [OSS不直接提供限制上传文件类型和大小的功能](https://help.aliyun.com/zh/oss/how-do-i-limit-object-formats-and-sizes-when-i-upload-objects-to-oss)
159    ///
160    /// # 参数
161    /// - `object_name`:要上传的对象名称。必须遵守 [OSS Object 命名规则](https://help.aliyun.com/zh/oss/user-guide/object-overview#720fde5f0asvg)。
162    /// - `expires`:URL 的有效期,单位为秒。过期后将无法使用。
163    ///
164    /// 阿里云oss文档不建议使用预签名URL上传带有回调的对象:
165    /// > 该方式常用于预签名URL上传文件的场景,通过将回调参数Base64编码后拼接在URL中实现自动回调。
166    /// > 但由于回调信息暴露在 URL 中,存在一定的安全风险,仅建议用于临时访问或低敏感场景。[callback签名](https://help.aliyun.com/zh/oss/developer-reference/callback)
167    ///
168    /// # 当使用 STS 临时访问凭证时的注意事项
169    /// - 本方法通过在 URL 中包含签名的方式进行授权,如果使用 STS 访问方式,会自动在查询参数中追加 `x-oss-security-token`,因此无需再在 Header 中携带该字段。
170    /// - 如需使用 POST 预签名表单上传,请根据 STS 凭证自行构造表单并在表单中携带 `x-oss-security-token` 字段。
171    /// - 其它正常的 API 请求(非预签名 URL 的方式),需要在 Header 中携带 `x-oss-security-token` 字段。
172    pub async fn generate_presigned_url(
173        &self,
174        object_name: &str,
175        expires: i32,
176    ) -> Result<String, Error> {
177        validate_object_name(object_name)?;
178
179        let client = self.client;
180        let mut base_url = url::Url::parse(&format!(
181            "https://{}.{}/{}",
182            client.bucket, client.endpoint, object_name
183        ))
184        .unwrap();
185
186        // callback处理
187        if let Some(oss_callback) = &self.callback {
188            let callback_base64 =
189                general_purpose::STANDARD.encode(serde_json::to_string(oss_callback).unwrap());
190            base_url
191                .query_pairs_mut()
192                .append_pair("callback", &callback_base64);
193            if !oss_callback.callback_body.callback_var.is_empty() {
194                let callback_var_map = oss_callback
195                    .callback_body
196                    .callback_var
197                    .iter()
198                    .map(|(_, k, v)| (k, v))
199                    .collect::<HashMap<_, _>>();
200                let callback_var_base64 = general_purpose::STANDARD
201                    .encode(serde_json::to_string(&callback_var_map).unwrap());
202                base_url
203                    .query_pairs_mut()
204                    .append_pair("callback-var", &callback_var_base64);
205            }
206        }
207
208        let creds = client.credentials_provider.load().await?;
209        // sts token
210        if let Some(token) = &creds.sts_security_token {
211            base_url
212                .query_pairs_mut()
213                .append_pair("x-oss-security-token", token);
214        }
215
216        let mut header_map: HashMap<String, String> =
217            serde_json::from_value(serde_json::to_value(self).unwrap()).unwrap();
218        if !self.custom_metas.is_empty() {
219            let custom_meta_map = self
220                .custom_metas
221                .iter()
222                .map(|(k, v)| (k.clone(), v.clone()))
223                .collect::<HashMap<_, _>>();
224            header_map.extend(custom_meta_map);
225        };
226
227        let presigned_params = PresignParams {
228            access_key_id: &creds.access_key_id,
229            access_key_secret: &creds.access_key_secret,
230            header_map,
231            presigned_url: base_url,
232            http_verb: HTTPVerb::Put,
233            url_expires: expires,
234            bucket: &client.bucket,
235            signing_region: &client.region,
236        };
237        let signed_url = generate_presigned_url(presigned_params);
238        Ok(signed_url)
239    }
240}
241
242impl PostObject<'_> {
243    /// 生成用于浏览器表单方式上传所需要的内容
244    ///
245    /// - [oss Post v4 签名文档](https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend)
246    /// - [PostObject API文档](https://help.aliyun.com/zh/oss/developer-reference/postobject)
247    ///
248    /// # 参数
249    /// - `expiration`:策略过期时间
250    ///
251    /// # 注意
252    /// 当你使用sts的方式生成policy后。在发起表单请求的时候请求头如果没有携带`x-oss-security-token`,会报`InvalidAccessKeyId`的错误
253    pub async fn generate_policy(
254        self,
255        expiration: OffsetDateTime,
256    ) -> Result<GeneratePolicyResult, Error> {
257        let policy_expiration = expiration.to_utc().format(&Iso8601::DEFAULT).unwrap();
258        let now = OffsetDateTime::now_utc();
259        // 这个date,需不需utc,文档没说...
260        let date = utc_date_str(&now);
261        let date_time = utc_date_time_str(&now);
262        let client = self.client;
263        let creds = client.credentials_provider.load().await?;
264        let credential = format!(
265            "{}/{}/{}/oss/aliyun_v4_request",
266            creds.access_key_id, date, client.region
267        );
268
269        // 处理callback相关
270        let mut callback_b64 = None;
271        let mut callback_var = None;
272        if let Some(oss_callback) = &self.callback {
273            callback_b64 = Some(
274                general_purpose::STANDARD.encode(serde_json::to_string(oss_callback).unwrap()),
275            );
276            if !oss_callback.callback_body.callback_var.is_empty() {
277                let callback_var_map = oss_callback
278                    .callback_body
279                    .callback_var
280                    .iter()
281                    .map(|(_, k, v)| (k.clone(), v.clone()))
282                    .collect::<HashMap<_, _>>();
283                callback_var = Some(callback_var_map);
284            }
285        }
286
287        let policy = PostPolicy {
288            expiration: policy_expiration,
289            conditions: PostPolicyCondition {
290                bucket: self.bucket,
291                x_oss_signature_version: "OSS4-HMAC-SHA256".to_owned(),
292                x_oss_credential: credential.clone(),
293                x_oss_security_token: creds.sts_security_token.clone(),
294                x_oss_date: date_time.clone(),
295                content_length_range: self.content_length_range,
296                key: self.key,
297                success_action_status: self.success_action_status,
298                content_type: self.content_type,
299                cache_control: self.cache_control,
300                expires: self.expires,
301                content_disposition: self.content_disposition,
302                content_encoding: self.content_encoding,
303                x_oss_object_acl: self.x_oss_object_acl,
304                x_oss_server_side_encryption_key_id: self.x_oss_server_side_encryption_key_id,
305                x_oss_server_side_data_encryption: self.x_oss_server_side_data_encryption,
306                x_oss_content_type: self.x_oss_content_type,
307                x_oss_forbid_overwrite: self.x_oss_forbid_overwrite,
308                x_oss_storage_class: self.x_oss_storage_class,
309                success_action_redirect: self.success_action_redirect,
310                custom_metas: self.custom_metas,
311                callback_b64: callback_b64.as_deref(),
312                callback_var: callback_var.as_ref(),
313            },
314        };
315        let policy_str = serde_json::to_string(&policy).unwrap();
316        let encoded_policy = general_purpose::STANDARD.encode(policy_str.as_bytes());
317        let date_key = hmac_sha256_bytes(
318            format!("aliyun_v4{}", creds.access_key_secret).as_bytes(),
319            &date,
320        );
321        let date_region_key = hmac_sha256_bytes(&date_key, &client.region);
322        let date_region_service_key = hmac_sha256_bytes(&date_region_key, "oss");
323        let signing_key = hmac_sha256_bytes(&date_region_service_key, "aliyun_v4_request");
324        let signature = hex::encode(hmac_sha256_bytes(&signing_key, &encoded_policy));
325
326        Ok(GeneratePolicyResult {
327            policy: encoded_policy,
328            x_oss_signature: signature,
329            x_oss_date: date_time,
330            x_oss_credential: credential,
331            x_oss_signature_version: "OSS4-HMAC-SHA256".to_owned(),
332            x_oss_security_token: creds.sts_security_token.clone(),
333            callback: callback_b64,
334            callback_var,
335        })
336    }
337}
338
339impl GetObject<'_> {
340    /// 返回:
341    /// - `Vec<u8>`:文件数据
342    /// - `HashMap<String, String>`:所有响应头
343    pub async fn receive_bytes(
344        &self,
345        object_name: &str,
346    ) -> Result<(Bytes, GetObjectResponseHeader, HeaderMap), Error> {
347        let (resp, response_header, header) = self.get_response(object_name).await?;
348        let data = resp.bytes().await?;
349
350        Ok((data, response_header, header))
351    }
352
353    pub async fn receive_bytes_stream(
354        &self,
355        object_name: &str,
356    ) -> Result<
357        (
358            impl Stream<Item = Result<Bytes, Error>> + use<>,
359            GetObjectResponseHeader,
360            HeaderMap,
361        ),
362        Error,
363    > {
364        let (resp, response_header, header) = self.get_response(object_name).await?;
365        let byte_stream = resp.bytes_stream().map(|item| item.map_err(Error::Reqwest));
366        Ok((byte_stream, response_header, header))
367    }
368
369    pub async fn download_to_file(
370        &self,
371        object_name: &str,
372        file_path: &Path,
373    ) -> Result<(GetObjectResponseHeader, HeaderMap), Error> {
374        let (mut resp, response_header, header) = self.get_response(object_name).await?;
375
376        let mut file = tokio::fs::File::create(file_path).await?;
377        while let Some(chunk) = resp.chunk().await? {
378            file.write_all(&chunk).await?;
379        }
380        file.flush().await?;
381
382        Ok((response_header, header))
383    }
384
385    /// 生成预签名URL
386    ///
387    /// - `expires`:URL过期时间,单位秒
388    ///
389    /// 使用长期访问密钥AccessKey生成签名URL,该字段取值要求:最小值为 1 秒,最大值为 604800秒( 7 天)。
390    ///
391    /// 使用STS临时访问凭证生成签名URL,该字段取值要求:最小值为 1 秒,最大有效时长为 43200秒( 12 小时)。
392    ///
393    /// [签名文档和说明](https://help.aliyun.com/zh/oss/developer-reference/add-signatures-to-urls)
394    pub async fn generate_presigned_url(
395        &self,
396        object_name: &str,
397        expires: i32,
398    ) -> Result<String, Error> {
399        validate_object_name(object_name)?;
400
401        let client = self.client;
402        let mut base_url = url::Url::parse(&format!(
403            "https://{}.{}/{}",
404            client.bucket, client.endpoint, object_name
405        ))
406        .unwrap();
407        // 先把所有query参数添加到url中,这样在签名的时候直接传递url即可获取所有query参数
408        let query_map: HashMap<String, String> =
409            serde_json::from_value(serde_json::to_value(self.queries_part()).unwrap()).unwrap();
410        for (k, v) in query_map.iter() {
411            base_url.query_pairs_mut().append_pair(k, v);
412        }
413        let creds = client.credentials_provider.load().await?;
414        if let Some(token) = &creds.sts_security_token {
415            base_url
416                .query_pairs_mut()
417                .append_pair("x-oss-security-token", token);
418        }
419
420        let header_map: HashMap<String, String> =
421            serde_json::from_value(serde_json::to_value(self.headers_part()).unwrap()).unwrap();
422        let presigned_params = PresignParams {
423            access_key_id: &creds.access_key_id,
424            access_key_secret: &creds.access_key_secret,
425            header_map,
426            presigned_url: base_url,
427            http_verb: HTTPVerb::Get,
428            url_expires: expires,
429            bucket: &client.bucket,
430            signing_region: &client.region,
431        };
432        let signed_url = generate_presigned_url(presigned_params);
433        Ok(signed_url)
434    }
435
436    async fn get_response(
437        &self,
438        object_name: &str,
439    ) -> Result<(reqwest::Response, GetObjectResponseHeader, HeaderMap), Error> {
440        validate_object_name(object_name)?;
441
442        let client = self.client;
443        let mut request_url = url::Url::parse(&format!(
444            "https://{}.{}/{}",
445            client.bucket, client.endpoint, object_name
446        ))
447        .unwrap();
448        let query_map: HashMap<String, String> =
449            serde_json::from_value(serde_json::to_value(self.queries_part()).unwrap()).unwrap();
450        for (k, v) in query_map.iter() {
451            request_url.query_pairs_mut().append_pair(k, v);
452        }
453
454        let mut req_header_map: HashMap<String, String> =
455            serde_json::from_value(serde_json::to_value(self.headers_part()).unwrap()).unwrap();
456        let creds = client.credentials_provider.load().await?;
457        if let Some(token) = &creds.sts_security_token {
458            req_header_map.insert("x-oss-security-token".to_owned(), token.clone());
459        }
460        let header_map = get_request_header(
461            &creds.access_key_id,
462            &creds.access_key_secret,
463            req_header_map,
464            &request_url,
465            HTTPVerb::Get,
466            &client.region,
467            Some(&client.bucket),
468        );
469
470        let resp = client
471            .http_client
472            .get(request_url)
473            .headers(header_map)
474            .send()
475            .await?;
476
477        let status = resp.status();
478        if !status.is_success() {
479            return Err(into_request_failed_error(resp).await);
480        }
481
482        let header = resp.headers().clone();
483        let (mut response_header, custom_meta_map) =
484            parse_get_object_response_header::<GetObjectResponseHeader>(&header);
485        if !custom_meta_map.is_empty() {
486            response_header.custom_x_oss_meta = custom_meta_map;
487        }
488
489        Ok((resp, response_header, header))
490    }
491}
492
493impl CopyObject<'_> {
494    /// - 不会对参数的bucket,endpoint,object_name,region进行合法性检查,需要自行保证
495    /// - `copy_object_dest_info`:如为None,将使用client中的提供的相关信息
496    pub async fn send(
497        &self,
498        dest_bucket: &str,
499        dest_object_name: &str,
500    ) -> Result<CopyObjectResult, Error> {
501        validate_object_name(dest_object_name)?;
502
503        let client = self.client;
504        let request_url = url::Url::parse(&format!(
505            "https://{}.{}/{}",
506            dest_bucket, client.endpoint, dest_object_name
507        ))
508        .unwrap();
509
510        let mut req_header_map: HashMap<String, String> =
511            serde_json::from_value(serde_json::to_value(self).unwrap()).unwrap();
512        let creds = client.credentials_provider.load().await?;
513        if let Some(token) = &creds.sts_security_token {
514            req_header_map.insert("x-oss-security-token".to_owned(), token.clone());
515        }
516        let header_map = get_request_header(
517            &creds.access_key_id,
518            &creds.access_key_secret,
519            req_header_map,
520            &request_url,
521            HTTPVerb::Put,
522            &client.region,
523            Some(&client.bucket),
524        );
525
526        let resp = client
527            .http_client
528            .put(request_url)
529            .headers(header_map)
530            .send()
531            .await?;
532
533        let data = parse_xml_response(resp).await?;
534        Ok(data)
535    }
536}
537
538impl AppendObject<'_> {
539    /// - 当创建一个新的Appendable Object的时候,`position`设为`0`
540    /// - 如果该object已存在,则`position`为该Object的字节大小,即此次append object的起始位置
541    pub async fn send(
542        &self,
543        object_name: &str,
544        position: u64,
545        data: Vec<u8>,
546    ) -> Result<(u64, String), Error> {
547        validate_object_name(object_name)?;
548
549        let client = self.client;
550        let request_url = url::Url::parse_with_params(
551            &format!(
552                "https://{}.{}/{}",
553                client.bucket, client.endpoint, object_name
554            ),
555            [("append", ""), ("position", &position.to_string())],
556        )
557        .unwrap();
558
559        let mut req_header_map: HashMap<String, String> =
560            serde_json::from_value(serde_json::to_value(self).unwrap()).unwrap();
561
562        if !self.custom_metas.is_empty() {
563            let custom_meta_map = self
564                .custom_metas
565                .iter()
566                .map(|(k, v)| (k.clone(), v.clone()))
567                .collect::<HashMap<_, _>>();
568            req_header_map.extend(custom_meta_map);
569        }
570
571        req_header_map.insert("content-md5".to_owned(), get_content_md5(&data));
572        req_header_map.insert("content-length".to_owned(), data.len().to_string());
573
574        let creds = client.credentials_provider.load().await?;
575        if let Some(token) = &creds.sts_security_token {
576            req_header_map.insert("x-oss-security-token".to_owned(), token.clone());
577        }
578
579        let header_map = get_request_header(
580            &creds.access_key_id,
581            &creds.access_key_secret,
582            req_header_map,
583            &request_url,
584            HTTPVerb::Post,
585            &client.region,
586            Some(&client.bucket),
587        );
588
589        let resp = client
590            .http_client
591            .post(request_url)
592            .headers(header_map)
593            .body(data)
594            .send()
595            .await?;
596
597        if !resp.status().is_success() {
598            return Err(into_request_failed_error(resp).await);
599        }
600
601        let next_position = resp
602            .headers()
603            .get("x-oss-next-append-position")
604            .unwrap()
605            .to_str()
606            .unwrap()
607            .parse::<u64>()
608            .unwrap();
609        let response_hash = resp
610            .headers()
611            .get("x-oss-hash-crc64ecma")
612            .unwrap()
613            .to_str()
614            .unwrap()
615            .to_owned();
616
617        Ok((next_position, response_hash))
618    }
619}
620
621impl DeleteMultipleObjects<'_> {
622    /// 如果`quiet`为`true`,响应体没有内容,返回None
623    /// 如果`quiet`为`false`,响应体包含删除结果,返回Some(DeleteResult)
624    pub async fn send(&self) -> Result<Option<DeleteResult>, Error> {
625        let client = self.client;
626
627        let request_url = url::Url::parse(&format!(
628            "https://{}.{}/?delete",
629            client.bucket, client.endpoint
630        ))
631        .unwrap();
632        let delete_req = DeleteMultipleObjectsRequest {
633            quiet: self.quiet,
634            object: &self.objects,
635        };
636        let req_body = quick_xml::se::to_string_with_root("Delete", &delete_req).unwrap();
637        let mut req_header_map = HashMap::new();
638        if let Some(encoding_type) = self.encoding_type {
639            req_header_map.insert("encoding-type".to_owned(), encoding_type.to_owned());
640        }
641        req_header_map.insert("content-length".to_owned(), req_body.len().to_string());
642        req_header_map.insert(
643            "content-md5".to_owned(),
644            get_content_md5(req_body.as_bytes()),
645        );
646
647        let creds = client.credentials_provider.load().await?;
648        if let Some(token) = &creds.sts_security_token {
649            req_header_map.insert("x-oss-security-token".to_owned(), token.clone());
650        }
651
652        let header_map = get_request_header(
653            &creds.access_key_id,
654            &creds.access_key_secret,
655            req_header_map,
656            &request_url,
657            HTTPVerb::Post,
658            &client.region,
659            Some(&client.bucket),
660        );
661
662        let resp = client
663            .http_client
664            .post(request_url)
665            .headers(header_map)
666            .body(req_body)
667            .send()
668            .await?;
669
670        // 如果是is_quiet为true的请求,返回的xml中没有删除结果,使用Option来简化处理
671        let data = parse_xml_response(resp).await?;
672        Ok(data)
673    }
674}
675
676impl HeadObject<'_> {
677    pub async fn send(
678        &self,
679        object_name: &str,
680    ) -> Result<(HeadObjectResponseHeader, HeaderMap), Error> {
681        validate_object_name(object_name)?;
682
683        let client = self.client;
684        let request_url = url::Url::parse(&format!(
685            "https://{}.{}/{}",
686            client.bucket, client.endpoint, object_name
687        ))
688        .unwrap();
689
690        let mut req_header_map: HashMap<String, String> =
691            serde_json::from_value(serde_json::to_value(self).unwrap()).unwrap();
692        let creds = client.credentials_provider.load().await?;
693        if let Some(token) = &creds.sts_security_token {
694            req_header_map.insert("x-oss-security-token".to_owned(), token.clone());
695        }
696
697        let header_map = get_request_header(
698            &creds.access_key_id,
699            &creds.access_key_secret,
700            req_header_map,
701            &request_url,
702            HTTPVerb::Head,
703            &client.region,
704            Some(&client.bucket),
705        );
706
707        let resp = client
708            .http_client
709            .head(request_url)
710            .headers(header_map)
711            .send()
712            .await?;
713
714        let status = resp.status();
715        if !status.is_success() {
716            return Err(into_request_failed_error(resp).await);
717        }
718
719        let header = resp.headers().clone();
720        let (mut response_header, custom_meta_map) =
721            parse_get_object_response_header::<HeadObjectResponseHeader>(&header);
722        if !custom_meta_map.is_empty() {
723            response_header.custom_x_oss_meta = custom_meta_map;
724        }
725        Ok((response_header, header))
726    }
727}
728
729/// Object基础操作
730impl Client {
731    pub fn put_object(&self) -> PutObjectBuilder<'_> {
732        PutObject::builder(self)
733    }
734
735    pub fn post_object(&self) -> PostObjectBuilder<'_> {
736        PostObject::builder(self)
737    }
738
739    pub fn get_object(&self) -> GetObjectBuilder<'_> {
740        GetObject::builder(self)
741    }
742
743    pub fn copy_object(&self) -> CopyObjectBuilder<'_> {
744        CopyObject::builder(self)
745    }
746
747    pub fn append_object(&self) -> AppendObjectBuilder<'_> {
748        AppendObject::builder(self)
749    }
750
751    /// 无论object是否存在都会执行删除操作并返回成功
752    pub async fn delete_object(
753        &self,
754        object_name: &str,
755    ) -> Result<DeleteObjectResponseHeader, Error> {
756        validate_object_name(object_name)?;
757
758        let request_url = url::Url::parse(&format!(
759            "https://{}.{}/{}",
760            self.bucket, self.endpoint, object_name
761        ))
762        .unwrap();
763
764        let creds = self.credentials_provider.load().await?;
765        let mut req_header_map = HashMap::new();
766        if let Some(token) = &creds.sts_security_token {
767            req_header_map.insert("x-oss-security-token".to_owned(), token.clone());
768        }
769
770        let header_map = get_request_header(
771            &creds.access_key_id,
772            &creds.access_key_secret,
773            req_header_map,
774            &request_url,
775            HTTPVerb::Delete,
776            &self.region,
777            Some(&self.bucket),
778        );
779
780        let resp = self
781            .http_client
782            .delete(request_url)
783            .headers(header_map)
784            .send()
785            .await?;
786
787        if !resp.status().is_success() {
788            return Err(into_request_failed_error(resp).await);
789        }
790
791        let x_oss_delete_marker = resp
792            .headers()
793            .get("x-oss-delete-marker")
794            .map(|v| v.to_str().unwrap().parse::<bool>().unwrap());
795        let x_oss_version_id = resp
796            .headers()
797            .get("x-oss-version-id")
798            .map(|v| v.to_str().unwrap().to_owned());
799
800        Ok(DeleteObjectResponseHeader {
801            x_oss_delete_marker,
802            x_oss_version_id,
803        })
804    }
805
806    pub fn delete_multiple_objects(&self) -> DeleteMultipleObjectsBuilder<'_> {
807        DeleteMultipleObjects::builder(self)
808    }
809
810    pub fn head_object(&self) -> HeadObjectBuilder<'_> {
811        HeadObject::builder(self)
812    }
813
814    /// - 这里返回`HashMap`而没有返回struct,主要考虑到response header中有一些参数文档中没说出来,不便于转化为指定的struct
815    /// - 返回的`HashMap`中所有的`key`均为小写,这里代码并没有使用`to_lowercase`,因为`reqwest`获取的header都为小写
816    pub async fn get_object_meta(
817        &self,
818        object_name: &str,
819    ) -> Result<GetObjectMetaResponseHeader, Error> {
820        validate_object_name(object_name)?;
821
822        let request_url = url::Url::parse(&format!(
823            "https://{}.{}/{}?objectMeta",
824            self.bucket, self.endpoint, object_name
825        ))
826        .unwrap();
827
828        let creds = self.credentials_provider.load().await?;
829        let mut req_header_map = HashMap::new();
830        if let Some(token) = &creds.sts_security_token {
831            req_header_map.insert("x-oss-security-token".to_owned(), token.clone());
832        }
833        let header_map = get_request_header(
834            &creds.access_key_id,
835            &creds.access_key_secret,
836            req_header_map,
837            &request_url,
838            HTTPVerb::Head,
839            &self.region,
840            Some(&self.bucket),
841        );
842
843        let resp = self
844            .http_client
845            .head(request_url)
846            .headers(header_map)
847            .send()
848            .await?;
849
850        let status = resp.status();
851        if !status.is_success() {
852            return Err(into_request_failed_error(resp).await);
853        }
854
855        let mut response_header = Map::new();
856        for (name, val) in resp.headers().iter() {
857            let name_s = name.as_str();
858            if let Ok(s) = val.to_str() {
859                response_header.insert(name_s.to_string(), Value::String(s.to_string()));
860            }
861        }
862
863        let data = serde_json::from_value(Value::Object(response_header)).unwrap();
864        Ok(data)
865    }
866}