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