dropbox_sdk/
default_async_client.rs

1//! The default async HTTP client.
2//!
3//! Use this client if you're not particularly picky about implementation details, as the specific
4//! implementation is not exposed, and may be changed in the future.
5//!
6//! If you have a need for a specific HTTP client implementation, or your program is already using
7//! some HTTP client crate, you probably want to have this Dropbox SDK crate use it as well. To do
8//! that, you should implement the traits in `crate::client_trait` for it and use it instead.
9//!
10//! This code (and its dependencies) are only built if you use the `default_async_client` Cargo
11//! feature.
12
13use crate::async_client_trait::{
14    AppAuthClient, HttpClient, HttpRequest, HttpRequestResultRaw, NoauthClient, TeamAuthClient,
15    TeamSelect, UserAuthClient,
16};
17use crate::default_client_common::impl_set_path_root;
18use crate::oauth2::{Authorization, TokenCache};
19use crate::Error;
20use bytes::Bytes;
21use futures::{FutureExt, TryFutureExt, TryStreamExt};
22use std::future::{ready, Future};
23use std::str::FromStr;
24use std::sync::Arc;
25
26macro_rules! impl_update_token {
27    ($self:ident) => {
28        fn update_token(&$self, old_token: Arc<String>)
29            -> impl Future<Output = Result<bool, Error>> + Send
30        {
31            info!("refreshing auth token");
32            $self.tokens
33                .update_token(
34                    TokenUpdateClient { inner: &$self.inner },
35                    old_token,
36                )
37                .map(|r| match r {
38                    Ok(_) => Ok(true),
39                    Err(e) => {
40                        error!("failed to update auth token: {e}");
41                        Err(e.into())
42                    }
43                })
44        }
45    };
46}
47
48/// Default HTTP client using User authorization.
49pub struct UserAuthDefaultClient {
50    inner: ReqwestClient,
51    tokens: Arc<TokenCache>,
52    path_root: Option<String>, // a serialized PathRoot enum
53}
54
55impl UserAuthDefaultClient {
56    /// Create a new client using the given OAuth2 authorization.
57    pub fn new(auth: Authorization) -> Self {
58        Self::from_token_cache(Arc::new(TokenCache::new(auth)))
59    }
60
61    /// Create a new client from a [`TokenCache`], which lets you share the same tokens between
62    /// multiple clients.
63    pub fn from_token_cache(tokens: Arc<TokenCache>) -> Self {
64        Self {
65            inner: Default::default(),
66            tokens,
67            path_root: None,
68        }
69    }
70
71    impl_set_path_root!(self);
72}
73
74impl HttpClient for UserAuthDefaultClient {
75    type Request = ReqwestRequest;
76
77    fn execute(
78        &self,
79        request: Self::Request,
80        body: Bytes,
81    ) -> impl Future<Output = Result<HttpRequestResultRaw, Error>> + Send {
82        self.inner.execute(request, body)
83    }
84
85    fn new_request(&self, url: &str) -> Self::Request {
86        self.inner.new_request(url)
87    }
88
89    impl_update_token!(self);
90
91    fn token(&self) -> Option<Arc<String>> {
92        self.tokens.get_token()
93    }
94
95    fn path_root(&self) -> Option<&str> {
96        self.path_root.as_deref()
97    }
98}
99
100impl UserAuthClient for UserAuthDefaultClient {}
101
102/// Default HTTP client using Team authorization.
103pub struct TeamAuthDefaultClient {
104    inner: ReqwestClient,
105    tokens: Arc<TokenCache>,
106    path_root: Option<String>, // a serialized PathRoot enum
107    team_select: Option<TeamSelect>,
108}
109
110impl TeamAuthDefaultClient {
111    /// Create a new client using the given OAuth2 token, with no user/admin context selected.
112    pub fn new(tokens: impl Into<Arc<TokenCache>>) -> Self {
113        Self {
114            inner: Default::default(),
115            tokens: tokens.into(),
116            path_root: None,
117            team_select: None,
118        }
119    }
120
121    /// Select a user or team context to operate in.
122    pub fn select(&mut self, team_select: Option<TeamSelect>) {
123        self.team_select = team_select;
124    }
125
126    impl_set_path_root!(self);
127}
128
129impl HttpClient for TeamAuthDefaultClient {
130    type Request = ReqwestRequest;
131
132    fn execute(
133        &self,
134        request: Self::Request,
135        body: Bytes,
136    ) -> impl Future<Output = Result<HttpRequestResultRaw, Error>> + Send {
137        self.inner.execute(request, body)
138    }
139
140    fn new_request(&self, url: &str) -> Self::Request {
141        self.inner.new_request(url)
142    }
143
144    fn token(&self) -> Option<Arc<String>> {
145        self.tokens.get_token()
146    }
147
148    impl_update_token!(self);
149
150    fn path_root(&self) -> Option<&str> {
151        self.path_root.as_deref()
152    }
153
154    fn team_select(&self) -> Option<&TeamSelect> {
155        self.team_select.as_ref()
156    }
157}
158
159impl TeamAuthClient for TeamAuthDefaultClient {}
160
161/// Default HTTP client using App authorization.
162#[derive(Debug)]
163pub struct AppAuthDefaultClient {
164    inner: ReqwestClient,
165    path_root: Option<String>,
166    auth: String,
167}
168
169impl AppAuthDefaultClient {
170    /// Create a new App auth client using the given app key and secret, which can be found in the Dropbox app console.
171    pub fn new(app_key: &str, app_secret: &str) -> Self {
172        use base64::prelude::*;
173        let encoded = BASE64_STANDARD.encode(format!("{app_key}:{app_secret}"));
174        Self {
175            inner: ReqwestClient::default(),
176            path_root: None,
177            auth: format!("Basic {encoded}"),
178        }
179    }
180
181    impl_set_path_root!(self);
182}
183
184impl HttpClient for AppAuthDefaultClient {
185    type Request = ReqwestRequest;
186
187    fn execute(
188        &self,
189        request: Self::Request,
190        body: Bytes,
191    ) -> impl Future<Output = Result<HttpRequestResultRaw, Error>> + Send {
192        self.inner.execute(request, body)
193    }
194
195    fn new_request(&self, url: &str) -> Self::Request {
196        self.inner
197            .new_request(url)
198            .set_header("Authorization", &self.auth)
199    }
200}
201
202impl AppAuthClient for AppAuthDefaultClient {}
203
204/// Default HTTP client for unauthenticated API calls.
205#[derive(Debug, Default)]
206pub struct NoauthDefaultClient {
207    inner: ReqwestClient,
208    path_root: Option<String>,
209}
210
211impl NoauthDefaultClient {
212    impl_set_path_root!(self);
213}
214
215impl HttpClient for NoauthDefaultClient {
216    type Request = ReqwestRequest;
217
218    fn execute(
219        &self,
220        request: Self::Request,
221        body: Bytes,
222    ) -> impl Future<Output = Result<HttpRequestResultRaw, Error>> + Send {
223        self.inner.execute(request, body)
224    }
225
226    fn new_request(&self, url: &str) -> Self::Request {
227        self.inner.new_request(url)
228    }
229
230    fn path_root(&self) -> Option<&str> {
231        self.path_root.as_deref()
232    }
233}
234
235impl NoauthClient for NoauthDefaultClient {}
236
237/// Same as NoauthDefaultClient but with inner by reference and no path_root.
238/// Only used for updating authorization tokens.
239struct TokenUpdateClient<'a> {
240    inner: &'a ReqwestClient,
241}
242
243impl HttpClient for TokenUpdateClient<'_> {
244    type Request = ReqwestRequest;
245
246    fn execute(
247        &self,
248        request: Self::Request,
249        body: Bytes,
250    ) -> impl Future<Output = Result<HttpRequestResultRaw, Error>> + Send {
251        self.inner.execute(request, body)
252    }
253
254    fn new_request(&self, url: &str) -> Self::Request {
255        self.inner.new_request(url)
256    }
257}
258
259impl NoauthClient for TokenUpdateClient<'_> {}
260
261#[derive(Debug)]
262struct ReqwestClient {
263    inner: reqwest::Client,
264}
265
266impl Default for ReqwestClient {
267    fn default() -> Self {
268        Self {
269            inner: reqwest::Client::builder()
270                .https_only(true)
271                .http2_prior_knowledge()
272                .build()
273                .unwrap(),
274        }
275    }
276}
277
278fn unexpected<T: std::error::Error + Send + Sync>(e: T, msg: &str) -> Error {
279    Error::UnexpectedResponse(format!("{msg}: {e}"))
280}
281
282impl HttpClient for ReqwestClient {
283    type Request = ReqwestRequest;
284
285    fn execute(
286        &self,
287        request: Self::Request,
288        body: Bytes,
289    ) -> impl Future<Output = Result<HttpRequestResultRaw, Error>> + Send {
290        let mut req = match request.req.build() {
291            Ok(req) => req,
292            Err(e) => {
293                return ready(Err(Error::HttpClient(Box::new(e)))).boxed();
294            }
295        };
296        debug!("request for {}", req.url());
297        if !body.is_empty() {
298            *req.body_mut() = Some(reqwest::Body::from(body));
299        }
300        self.inner
301            .execute(req)
302            .map_ok_or_else(
303                |e| Err(Error::HttpClient(Box::new(e))),
304                |resp| {
305                    let status = resp.status().as_u16();
306
307                    let result_header = resp
308                        .headers()
309                        .get("Dropbox-API-Result")
310                        .map(|v| v.to_str())
311                        .transpose()
312                        .map_err(|e| unexpected(e, "invalid Dropbox-API-Result header"))?
313                        .map(ToOwned::to_owned);
314
315                    let content_length = resp
316                        .headers()
317                        .get("Content-Length")
318                        .map(|v| {
319                            v.to_str()
320                                .map_err(|e| unexpected(e, "invalid Content-Length"))
321                                .and_then(|s| {
322                                    u64::from_str(s)
323                                        .map_err(|e| unexpected(e, "invalid Content-Length"))
324                                })
325                        })
326                        .transpose()?;
327
328                    let body = resp
329                        .bytes_stream()
330                        .map_err(futures::io::Error::other)
331                        .into_async_read();
332
333                    Ok(HttpRequestResultRaw {
334                        status,
335                        result_header,
336                        content_length,
337                        body: Box::new(body),
338                    })
339                },
340            )
341            .boxed()
342    }
343
344    fn new_request(&self, url: &str) -> Self::Request {
345        ReqwestRequest {
346            req: self.inner.post(url),
347        }
348    }
349}
350
351/// This is an implementation detail of the HTTP client.
352pub struct ReqwestRequest {
353    req: reqwest::RequestBuilder,
354}
355
356impl HttpRequest for ReqwestRequest {
357    fn set_header(mut self, name: &str, value: &str) -> Self {
358        self.req = self.req.header(name, value);
359        self
360    }
361}