Skip to main content

ufile_rus3/
client.rs

1use std::{collections::HashMap, str::FromStr, time::Duration};
2
3use crate::{
4    AuthorizationService,
5    api::{
6        BaseResponse, ByteStream, GenPrivateUrlRequestBuilder, HeadFileRequestBuilder,
7        MultipartAbortRequestBuilder, MultipartFileRequestBuilder, MultipartFinishRequestBuilder,
8        MultipartInitRequestBuilder, ObjectConfig, ProgressStream, PutFileRequestBuilder,
9    },
10};
11use anyhow::Error;
12use reqwest::{Body, Client, ClientBuilder, Method, Proxy, Url, header::HeaderMap};
13
14#[derive(Clone)]
15pub struct S3Client {
16    http_client: HttpClient,
17    auth_service: AuthorizationService,
18}
19
20impl S3Client {
21    pub fn new() -> Self {
22        Self {
23            http_client: HttpClientBuilder::default().build().unwrap(),
24            auth_service: AuthorizationService,
25        }
26    }
27
28    pub fn with_http_client(mut self, http_client: HttpClient) -> Self {
29        self.http_client = http_client;
30        self
31    }
32
33    pub fn with_auth_service(mut self, auth_service: AuthorizationService) -> Self {
34        self.auth_service = auth_service;
35        self
36    }
37
38    pub fn http_client(&self) -> HttpClient {
39        self.http_client.clone()
40    }
41
42    pub fn authorization_service(&self) -> AuthorizationService {
43        self.auth_service
44    }
45
46    /// Put object request builder.
47    #[must_use]
48    pub fn put_object(&self, object_config: ObjectConfig) -> PutFileRequestBuilder {
49        PutFileRequestBuilder::default()
50            .object_config(object_config)
51            .client(self.http_client())
52    }
53
54    /// Init multipart upload request builder.
55    pub fn multipart_init(&self, object_config: ObjectConfig) -> MultipartInitRequestBuilder {
56        MultipartInitRequestBuilder::default()
57            .object_config(object_config)
58            .client(self.http_client())
59    }
60
61    /// Upload multipart file slice request builder.
62    pub fn multipart_upload(&self, object_config: ObjectConfig) -> MultipartFileRequestBuilder {
63        MultipartFileRequestBuilder::default()
64            .object_config(object_config)
65            .client(self.http_client())
66    }
67
68    /// Finish multipart upload request builder.
69    pub fn multipart_finish(&self, object_config: ObjectConfig) -> MultipartFinishRequestBuilder {
70        MultipartFinishRequestBuilder::default()
71            .object_config(object_config)
72            .client(self.http_client())
73    }
74
75    /// Abort multipart upload request builder.
76    pub fn multipart_abort(&self, object_config: ObjectConfig) -> MultipartAbortRequestBuilder {
77        MultipartAbortRequestBuilder::default()
78            .object_config(object_config)
79            .client(self.http_client())
80    }
81
82    /// Get file heads request builder.
83    pub fn head_object(&self, object_config: ObjectConfig) -> HeadFileRequestBuilder {
84        HeadFileRequestBuilder::default()
85            .object_config(object_config)
86            .client(self.http_client())
87    }
88
89    /// Generate private url request builder.
90    pub fn gen_private_url(&self) -> GenPrivateUrlRequestBuilder {
91        GenPrivateUrlRequestBuilder::default()
92    }
93}
94
95#[repr(transparent)]
96#[derive(Clone)]
97pub struct HttpClient {
98    inner: Client,
99}
100
101pub struct HttpClientBuilder {
102    builder: ClientBuilder,
103}
104
105impl HttpClient {
106    pub fn builder() -> HttpClientBuilder {
107        HttpClientBuilder::new()
108    }
109
110    pub fn get_client(&self) -> &Client {
111        &self.inner
112    }
113
114    pub fn into_inner(self) -> Client {
115        self.inner
116    }
117}
118
119impl HttpClientBuilder {
120    pub fn new() -> Self {
121        Self {
122            builder: ClientBuilder::new()
123                .connect_timeout(Duration::from_secs(5))
124                // read timeout set to 30 seconds.
125                .read_timeout(Duration::from_secs(30))
126                .timeout(Duration::from_secs(3600))
127                // 5 minutes not working then release connection.
128                .pool_idle_timeout(Duration::from_secs(300))
129                .pool_max_idle_per_host(5)
130                // only support http1
131                .http1_only()
132                .user_agent(format!("ufile-rus3-sdk/{}", crate::VERSION)),
133        }
134    }
135
136    pub fn with_timeout(mut self, timeout: Duration) -> Self {
137        self.builder = self.builder.timeout(timeout);
138        self
139    }
140
141    pub fn with_connect_timeout(mut self, connect_timeout: Duration) -> Self {
142        self.builder = self.builder.connect_timeout(connect_timeout);
143        self
144    }
145
146    pub fn with_headers(mut self, headers: HeaderMap) -> Self {
147        self.builder = self.builder.default_headers(headers);
148        self
149    }
150
151    pub fn with_proxy(mut self, proxy: Proxy) -> Self {
152        self.builder = self.builder.proxy(proxy);
153        self
154    }
155
156    pub fn with_pool_idle_timeout(mut self, pool_idle_timeout: Duration) -> Self {
157        self.builder = self.builder.pool_idle_timeout(pool_idle_timeout);
158        self
159    }
160
161    pub fn with_read_timeout(mut self, read_timeout: Duration) -> Self {
162        self.builder = self.builder.read_timeout(read_timeout);
163        self
164    }
165
166    pub fn with_max_idle_per_host(mut self, max_idle_per_host: usize) -> Self {
167        self.builder = self.builder.pool_max_idle_per_host(max_idle_per_host);
168        self
169    }
170
171    pub fn build(self) -> Result<HttpClient, Error> {
172        Ok(HttpClient {
173            inner: self.builder.build()?,
174        })
175    }
176}
177impl Default for HttpClientBuilder {
178    fn default() -> Self {
179        Self::new()
180    }
181}
182impl HttpClient {
183    /// This method support only files which is smaller than 512MB,
184    /// otherwise, will return error.
185    /// If you are trying to upload a file that is more than 512MB, please use multipart upload which
186    /// is supported by ucloud (Which should create multiple slices to upload).
187    pub async fn send_file(
188        &self,
189        url: &str,
190        method: Method,
191        headers: HeaderMap,
192        stream: ByteStream,
193    ) -> Result<BaseResponse, Error> {
194        // Check authorization
195        let signature = headers.get("Authorization");
196        if signature.is_none() {
197            return Err(Error::msg("No authorization header found"));
198        }
199        let response = self
200            .inner
201            .request(method, Url::from_str(url)?)
202            .headers(headers)
203            .body(Body::wrap_stream(ProgressStream::from(stream)))
204            .send()
205            .await?;
206        tracing::debug!("send file response: {:?}", response);
207        let response_headers = response
208            .headers()
209            .iter()
210            .map(|(key, value)| Ok((key.to_string(), String::from_utf8(value.as_bytes().into())?)))
211            .collect::<Result<HashMap<String, String>, Error>>()?;
212        let status = response.status();
213        Ok(if status.is_success() {
214            // 2xx
215            BaseResponse {
216                headers: response_headers,
217                ret_code: 0,
218                message: None,
219            }
220        } else {
221            response.json::<BaseResponse>().await?
222        })
223    }
224}