• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tu6ge / oss-rs / 5829505807

pending completion
5829505807

push

github

tu6ge
Support std IO (#26)

* feat(decode)!: change init object fn

* todo

* feat(error): OssError add more info

when OssError code is SignatureDoesNotMatch ,show expect
 sign string

* feat(io): support write

* feat: blocking support

* feat: blocking read

* feat: 允许读取的数据于目标数组长度不一致

* feat: 分离 Rc 和内部数据

* feat: support Arc Object Content

* feat: 解决多次写入少量数据导致oss错误的问题

当多次写入少量数据,不符合分片的最小数量时,调用 oss 接口会导致报错

* refactor

* feat: 交互 arc 与 rc 的位置

* docs(io)

* docs(io)

* style

* chore: default close blocking

* fix

* style

* feat(io): change seek

* feat(io): change error type

* style

* feat(bucket)!: change base_bucket_info

* test(io)

* test(doc): remove deprecated

* test(io)

* test(io)

* test(io)

* style(io): clippy

* chore: support more derive

* refactor

* docs

1293 of 1293 new or added lines in 19 files covered. (100.0%)

7298 of 7685 relevant lines covered (94.96%)

9.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.8
/src/client.rs
1
//! # 对 reqwest 进行了简单的封装,加上了 OSS 的签名验证功能
×
2

3
use crate::auth::AuthBuilder;
4
#[cfg(feature = "blocking")]
5
use crate::blocking::builder::ClientWithMiddleware as BlockingClientWithMiddleware;
6
#[cfg(test)]
7
use crate::builder::Middleware;
8
use crate::builder::{ArcPointer, BuilderError, ClientWithMiddleware, RequestBuilder};
9
use crate::config::{get_bucket, get_endpoint, get_env, BucketBase, Config, InvalidConfig};
10
use crate::consts::{TRUE1, TRUE2, TRUE3, TRUE4};
11
use crate::file::AlignBuilder;
12
use crate::types::{
13
    object::{InvalidObjectPath, ObjectBase, ObjectPath},
14
    BucketName, CanonicalizedResource, EndPoint, KeyId, KeySecret,
15
};
16

17
use chrono::{DateTime, Utc};
18
use http::{
19
    header::{HeaderMap, HeaderName},
20
    HeaderValue, Method,
21
};
22
use reqwest::Url;
23
use std::env;
24
#[cfg(all(feature = "blocking", test))]
25
use std::rc::Rc;
26
#[cfg(test)]
27
use std::sync::Arc;
28
use std::time::Duration;
29

30
/// # 构造请求的客户端结构体
31
#[non_exhaustive]
32
#[derive(Default, Debug, Clone, PartialEq, Eq)]
136✔
33
pub struct Client<M = ClientWithMiddleware> {
34
    auth_builder: AuthBuilder,
68✔
35
    client_middleware: M,
68✔
36
    pub(crate) endpoint: EndPoint,
68✔
37
    pub(crate) bucket: BucketName,
68✔
38
    timeout: Option<Duration>,
68✔
39
}
40

41
impl<M> AsMut<Option<Duration>> for Client<M> {
42
    fn as_mut(&mut self) -> &mut Option<Duration> {
43
        &mut self.timeout
44
    }
45
}
46

47
impl<M> AsRef<EndPoint> for Client<M> {
48
    fn as_ref(&self) -> &EndPoint {
33✔
49
        &self.endpoint
33✔
50
    }
33✔
51
}
52
impl<M> AsRef<BucketName> for Client<M> {
53
    fn as_ref(&self) -> &BucketName {
33✔
54
        &self.bucket
55
    }
33✔
56
}
57
impl<M> AsMut<BucketName> for Client<M> {
58
    fn as_mut(&mut self) -> &mut BucketName {
59
        &mut self.bucket
60
    }
61
}
62

63
impl<M: Default> Client<M> {
64
    /// 使用基本配置信息初始化 Client
65
    pub fn new(
67✔
66
        access_key_id: KeyId,
67
        access_key_secret: KeySecret,
68
        endpoint: EndPoint,
69
        bucket: BucketName,
70
    ) -> Self {
71
        let mut auth_builder = AuthBuilder::default();
67✔
72
        auth_builder.key(access_key_id);
67✔
73
        auth_builder.secret(access_key_secret);
67✔
74

75
        Self::from_builder(auth_builder, endpoint, bucket)
67✔
76
    }
67✔
77

78
    /// - bucket: bar
79
    /// - endpoint: qingdao
80
    #[cfg(test)]
81
    pub fn test_init() -> Self {
30✔
82
        Self::new(
30✔
83
            "foo1".into(),
30✔
84
            "foo2".into(),
30✔
85
            EndPoint::CN_QINGDAO,
30✔
86
            "bar".try_into().unwrap(),
30✔
87
        )
88
    }
30✔
89

90
    /// 使用 [`Config`] 中的配置初始化 Client
91
    ///
92
    /// [`Config`]: crate::config::Config
93
    pub fn from_config(config: Config) -> Self {
3✔
94
        let (key, secret, bucket, endpoint) = config.get_all();
3✔
95

96
        let mut auth_builder = AuthBuilder::default();
3✔
97
        auth_builder.key(key);
3✔
98
        auth_builder.secret(secret);
3✔
99

100
        Self::from_builder(auth_builder, endpoint, bucket)
3✔
101
    }
3✔
102

103
    /// # 通过环境变量初始化 Client
104
    ///
105
    /// 如果在 Aliyun ECS 上,可将环境变量 `ALIYUN_OSS_INTERNAL`
106
    /// 设置为 `true` / `1` / `yes` / `Y` ,即可使用 internal 网络请求 OSS 接口
107
    ///
108
    /// 示例
109
    /// ```rust
110
    /// use std::env::set_var;
111
    /// set_var("ALIYUN_KEY_ID", "foo1");
112
    /// set_var("ALIYUN_KEY_SECRET", "foo2");
113
    /// set_var("ALIYUN_ENDPOINT", "qingdao");
114
    /// set_var("ALIYUN_BUCKET", "foo4");
115
    ///
116
    /// # use aliyun_oss_client::client::Client;
117
    /// use aliyun_oss_client::builder::ClientWithMiddleware;
118
    /// let client = Client::<ClientWithMiddleware>::from_env();
119
    /// assert!(client.is_ok());
120
    /// ```
121
    pub fn from_env() -> Result<Self, InvalidConfig> {
6✔
122
        let key_id = get_env("ALIYUN_KEY_ID")?;
6✔
123
        let key_secret = get_env("ALIYUN_KEY_SECRET")?;
6✔
124
        let endpoint = get_env("ALIYUN_ENDPOINT")?;
6✔
125
        let bucket = get_env("ALIYUN_BUCKET")?;
6✔
126

127
        let mut auth_builder = AuthBuilder::default();
6✔
128
        auth_builder.key(key_id);
6✔
129
        auth_builder.secret(key_secret);
6✔
130

131
        let mut endpoint = get_endpoint(&endpoint)?;
6✔
132

133
        if let Ok(is_internal) = env::var("ALIYUN_OSS_INTERNAL") {
6✔
134
            if is_internal == TRUE1
5✔
135
                || is_internal == TRUE2
4✔
136
                || is_internal == TRUE3
3✔
137
                || is_internal == TRUE4
2✔
138
            {
139
                endpoint.set_internal(true);
4✔
140
            }
141
        }
6✔
142

143
        Ok(Self::from_builder(
6✔
144
            auth_builder,
6✔
145
            endpoint,
6✔
146
            get_bucket(&bucket)?,
6✔
147
        ))
×
148
    }
6✔
149

150
    #[doc(hidden)]
151
    #[inline]
152
    pub fn from_builder(auth_builder: AuthBuilder, endpoint: EndPoint, bucket: BucketName) -> Self {
77✔
153
        Self {
77✔
154
            auth_builder,
155
            client_middleware: M::default(),
77✔
156
            endpoint,
157
            bucket,
158
            timeout: None,
77✔
159
        }
160
    }
77✔
161
}
162
impl<M> Client<M> {
163
    pub(crate) fn get_bucket_name(&self) -> &BucketName {
18✔
164
        &self.bucket
165
    }
18✔
166

167
    /// 返回默认可用区,默认 bucket 的 BucketBase
168
    pub fn get_bucket_base(&self) -> BucketBase {
32✔
169
        BucketBase::new(self.bucket.to_owned(), self.endpoint.to_owned())
32✔
170
    }
32✔
171

172
    /// 获取默认的 bucket 的 url
173
    pub fn get_bucket_url(&self) -> Url {
25✔
174
        self.get_bucket_base().to_url()
25✔
175
    }
25✔
176

177
    pub(crate) fn get_key(&self) -> &KeyId {
1✔
178
        self.auth_builder.get_key()
1✔
179
    }
1✔
180
    pub(crate) fn get_secret(&self) -> &KeySecret {
1✔
181
        self.auth_builder.get_secret()
1✔
182
    }
1✔
183

184
    // pub(crate) fn get_endpoint(&self) -> &EndPoint {
185
    //     &self.endpoint
186
    // }
187

188
    /// 获取默认的可用区的 url
189
    pub fn get_endpoint_url(&self) -> Url {
3✔
190
        self.endpoint.to_url()
3✔
191
    }
3✔
192

193
    /// 更改默认 bucket
194
    pub fn set_bucket(&mut self, bucket: BucketName) {
195
        self.bucket = bucket;
196
    }
197

198
    /// 更改默认 endpoint
199
    pub fn set_endpoint(&mut self, endpoint: EndPoint) {
200
        self.endpoint = endpoint;
201
    }
202

203
    /// 设置 timeout
204
    pub fn timeout(&mut self, timeout: Duration) {
3✔
205
        self.timeout = Some(timeout);
3✔
206
    }
3✔
207

208
    /// 根据默认的 bucket,endpoint 和提供的文件路径,获取 ObjectBase
209
    #[inline]
210
    pub fn get_object_base<P>(&self, path: P) -> Result<ObjectBase, InvalidObjectPath>
3✔
211
    where
212
        P: TryInto<ObjectPath>,
213
        P::Error: Into<InvalidObjectPath>,
214
    {
215
        ObjectBase::<ArcPointer>::from_bucket(self.get_bucket_base(), path)
3✔
216
    }
3✔
217
}
218

219
#[cfg(not(test))]
220
#[inline]
221
fn now() -> DateTime<Utc> {
19✔
222
    Utc::now()
19✔
223
}
19✔
224

225
#[cfg(test)]
226
fn now() -> DateTime<Utc> {
46✔
227
    use chrono::NaiveDateTime;
228
    let naive = NaiveDateTime::parse_from_str("2022/10/6 20:40:00", "%Y/%m/%d %H:%M:%S").unwrap();
46✔
229
    DateTime::from_utc(naive, Utc)
46✔
230
}
46✔
231

232
/// 异步 Client 别名
233
pub type ClientArc = Client<ClientWithMiddleware>;
234

235
impl Client {
236
    /// # 用于模拟请求 OSS 接口
237
    /// 默认直接请求 OSS 接口,如果设置中间件,则可以中断请求,对 Request 做一些断言,对 Response 做一些模拟操作
238
    #[cfg(test)]
239
    pub(crate) fn middleware(mut self, middleware: Arc<dyn Middleware>) -> Self {
18✔
240
        self.client_middleware.middleware(middleware);
18✔
241
        self
18✔
242
    }
18✔
243
}
244

245
impl AlignBuilder for Client<ClientWithMiddleware> {
246
    /// # 构造自定义的接口请求方法
247
    /// 比如在上传完文件时,返回自己期望的数据,而不是仅返回 etag 信息
248
    ///
249
    /// ## 例子是一个获取 object 元信息的接口
250
    /// ```
251
    /// use aliyun_oss_client::{errors::OssError, file::AlignBuilder, Client, Method};
252
    /// use dotenv::dotenv;
253
    ///
254
    /// async fn run() -> Result<(), OssError> {
255
    ///     dotenv().ok();
256
    ///     let client = Client::from_env().unwrap();
257
    ///
258
    ///     let (url, resource) = client
259
    ///         .get_object_base("9AB932LY.jpeg")?
260
    ///         .get_url_resource([]);
261
    ///
262
    ///     let headers = vec![(
263
    ///         "If-Unmodified-Since".parse().unwrap(),
264
    ///         "Sat, 01 Jan 2022 18:01:01 GMT".parse().unwrap(),
265
    ///     )];
266
    ///
267
    ///     let builder = client.builder_with_header(Method::HEAD, url, resource, headers)?;
268
    ///
269
    ///     let response = builder.send().await?;
270
    ///
271
    ///     println!("status: {:?}", response.status());
272
    ///
273
    ///     Ok(())
274
    /// }
275
    /// ```
276
    /// ## 参数
277
    /// - method 接口请求方式
278
    /// - url 要请求的接口,包含 query 参数等信息
279
    /// - resource 阿里云接口需要提供的统一的信息,[`CanonicalizedResource`] 提供了 bucket ,object 等各种生成方式,如果无法满足
280
    /// 还可以自己用 trait 来自定义
281
    ///
282
    /// ## 返回值
283
    /// 返回值是一个封装了 reqwest::Builder 构造器,[`RequestBuilder`], 提供两个方法 `send` 和 `send_adjust_error`
284
    ///
285
    /// - `send` 方法,直接返回 `reqwest::Response`
286
    /// - `send_adjust_error` 方法,会对 api 返回结果进行处理,如果 HTTP 状态码正常(200>= && <300) 则,返回 Ok,
287
    /// 否则,会对返回的 xml 异常数据进行解析,返回 Err([`OssService`])
288
    ///
289
    /// [`RequestBuilder`]: crate::builder::RequestBuilder
290
    /// [`OssService`]: crate::errors::OssService
291
    /// [`CanonicalizedResource`]: crate::types::CanonicalizedResource
292
    fn builder_with_header<H: IntoIterator<Item = (HeaderName, HeaderValue)>>(
37✔
293
        &self,
294
        method: Method,
295
        url: Url,
296
        resource: CanonicalizedResource,
297
        headers: H,
298
    ) -> Result<RequestBuilder, BuilderError> {
299
        // dbg!(url.clone());
300
        // dbg!(resource.clone());
301
        let mut auth_builder = self.auth_builder.clone();
37✔
302
        auth_builder.method(&method);
37✔
303
        auth_builder.date(now());
37✔
304
        auth_builder.canonicalized_resource(resource);
37✔
305
        auth_builder.extend_headers(HeaderMap::from_iter(headers));
37✔
306

307
        let mut builder = self
37✔
308
            .client_middleware
309
            .request(method, url)
310
            .headers(auth_builder.get_headers()?);
37✔
311

312
        if let Some(timeout) = self.timeout {
37✔
313
            builder = builder.timeout(timeout);
1✔
314
        };
315

316
        Ok(builder)
37✔
317
    }
37✔
318
}
319

320
#[cfg(all(feature = "blocking", test))]
321
use crate::blocking::builder::Middleware as BlockingMiddleware;
322
#[cfg(feature = "blocking")]
323
use crate::blocking::builder::RequestBuilder as BlockingRequestBuilder;
324

325
/// 同步的 Client 别名
326
#[cfg(feature = "blocking")]
327
pub type ClientRc = Client<BlockingClientWithMiddleware>;
328

329
#[cfg(feature = "blocking")]
330
impl Client<BlockingClientWithMiddleware> {
331
    /// # 用于模拟请求 OSS 接口
332
    /// 默认直接请求 OSS 接口,如果设置中间件,则可以中断请求,对 Request 做一些断言,对 Response 做一些模拟操作
333
    #[cfg(test)]
334
    pub(crate) fn middleware(mut self, middleware: Rc<dyn BlockingMiddleware>) -> Self {
16✔
335
        self.client_middleware.middleware(middleware);
16✔
336
        self
16✔
337
    }
16✔
338
}
339

340
#[cfg(feature = "blocking")]
341
impl crate::file::blocking::AlignBuilder for Client<BlockingClientWithMiddleware> {
342
    /// # 向 OSS 发送请求的封装
343
    /// 参数包含请求的:
344
    ///
345
    /// - method
346
    /// - url
347
    /// - headers (可选)
348
    /// - [CanonicalizedResource](https://help.aliyun.com/document_detail/31951.html#section-rvv-dx2-xdb)
349
    ///
350
    /// 返回值是一个 reqwest 的请求创建器 `reqwest::blocking::RequestBuilder`
351
    ///
352
    /// 返回后,可以再加请求参数,然后可选的进行发起请求
353
    #[inline]
354
    fn builder_with_header<H: IntoIterator<Item = (HeaderName, HeaderValue)>>(
28✔
355
        &self,
356
        method: Method,
357
        url: Url,
358
        resource: CanonicalizedResource,
359
        headers: H,
360
    ) -> Result<BlockingRequestBuilder, BuilderError> {
361
        let method = method;
28✔
362
        let mut auth_builder = self.auth_builder.clone();
28✔
363
        auth_builder.method(&method);
28✔
364
        auth_builder.date(now());
28✔
365
        auth_builder.canonicalized_resource(resource);
28✔
366
        auth_builder.extend_headers(HeaderMap::from_iter(headers));
28✔
367

368
        let mut builder = self
28✔
369
            .client_middleware
370
            .request(method, url)
371
            .headers(auth_builder.get_headers()?);
28✔
372

373
        if let Some(timeout) = self.timeout {
28✔
374
            builder = builder.timeout(timeout);
1✔
375
        };
376

377
        Ok(builder)
28✔
378
    }
28✔
379
}
380

381
#[cfg(test)]
382
mod tests {
383
    use http::Method;
384

385
    use super::*;
386
    use crate::{
387
        builder::ArcPointer,
388
        config::{BucketBase, Config},
389
        file::AlignBuilder,
390
        types::object::ObjectBase,
391
        BucketName,
392
    };
393

394
    use std::time::Duration;
395

396
    #[test]
397
    fn from_config() {
2✔
398
        let config = Config::try_new("foo1", "foo2", "qingdao", "foo4").unwrap();
1✔
399
        let client = ClientArc::from_config(config);
1✔
400

401
        assert_eq!(client.bucket, "foo4".parse::<BucketName>().unwrap());
1✔
402
    }
2✔
403

404
    #[test]
405
    fn timeout() {
2✔
406
        let config = Config::try_new("foo1", "foo2", "qingdao", "foo4").unwrap();
1✔
407
        let mut client = ClientArc::from_config(config);
1✔
408

409
        assert!(client.timeout.is_none());
1✔
410

411
        client.timeout(Duration::new(10, 0));
1✔
412

413
        assert!(client.timeout.is_some());
1✔
414

415
        assert_eq!(client.timeout, Some(Duration::new(10, 0)));
1✔
416
    }
2✔
417

418
    #[test]
419
    fn test_timeout_with_builder() {
2✔
420
        let mut client = ClientArc::test_init();
1✔
421
        client.timeout(Duration::new(11, 0));
1✔
422
        let (url, resource) = client
1✔
423
            .get_object_base("9AB932LY.jpeg")
424
            .unwrap()
425
            .get_url_resource([]);
1✔
426
        let builder = client.builder_with_header(Method::HEAD, url, resource, []);
1✔
427
        let builder = builder.unwrap();
1✔
428

429
        let request = builder.build().unwrap();
1✔
430
        let timeout = request.timeout().unwrap().to_owned();
1✔
431
        assert_eq!(timeout, Duration::new(11, 0));
1✔
432
    }
2✔
433

434
    #[cfg(feature = "blocking")]
435
    #[test]
436
    fn test_timeout_with_builder_blocking() {
2✔
437
        use crate::file::blocking::AlignBuilder;
438

439
        let mut client = ClientRc::test_init();
1✔
440
        client.timeout(Duration::new(11, 0));
1✔
441
        let (url, resource) = client
1✔
442
            .get_object_base("9AB932LY.jpeg")
443
            .unwrap()
444
            .get_url_resource([]);
1✔
445
        let builder = client.builder_with_header(Method::HEAD, url, resource, []);
1✔
446
        let builder = builder.unwrap();
1✔
447

448
        let request = builder.build().unwrap();
1✔
449
        let timeout = request.timeout().unwrap().to_owned();
1✔
450
        assert_eq!(timeout, Duration::new(11, 0));
1✔
451
    }
2✔
452

453
    #[test]
454
    fn get_object_base() {
2✔
455
        use std::sync::Arc;
456

457
        let config = Config::try_new("foo1", "foo2", "qingdao", "foo4").unwrap();
1✔
458
        let client = ClientArc::from_config(config);
1✔
459

460
        let base = client.get_object_base("file111").unwrap();
1✔
461

462
        let base2 = ObjectBase::<ArcPointer>::new(
1✔
463
            Arc::new(BucketBase::new(
1✔
464
                "foo4".parse().unwrap(),
1✔
465
                "qingdao".parse().unwrap(),
1✔
466
            )),
467
            "file111",
468
        )
469
        .unwrap();
470
        assert!(base == base2);
1✔
471
    }
2✔
472
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc