b2_client/
client.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2   License, v. 2.0. If a copy of the MPL was not distributed with this
3   file, You can obtain one at http://mozilla.org/MPL/2.0/.
4*/
5
6//! HTTP client wrappers.
7//!
8//! Errors from the backend HTTP client are passed to user code, so dealing with
9//! errors is inconsistent between implementations; if you switch from one
10//! backend to another, your code that inspects errors will need to be updated.
11//!
12//! To use a custom HTTP client backend, implement [HttpClient] over an object
13//! that wraps your client.
14
15use std::{
16    collections::HashMap,
17    path::PathBuf,
18};
19
20use crate::error::ValidationError;
21
22#[cfg(feature = "with_surf")]
23pub use surf_client::SurfClient;
24
25#[cfg(feature = "with_hyper")]
26pub use hyper_client::HyperClient;
27
28#[cfg(feature = "with_isahc")]
29pub use isahc_client::IsahcClient;
30
31/// A trait that wraps an HTTP client to send HTTP requests.
32#[async_trait::async_trait]
33pub trait HttpClient
34    where Self: Default + Clone + Sized,
35{
36    /// The HTTP client's Error type.
37    type Error;
38
39    /// Create an HTTP `GET` request to the specified URL.
40    fn get(&mut self, url: impl AsRef<str>)
41    -> Result<&mut Self, ValidationError>;
42    /// Create an HTTP `HEAD` request to the specified URL.
43    fn head(&mut self, url: impl AsRef<str>)
44    -> Result<&mut Self, ValidationError>;
45    /// Create an HTTP `POST` request to the specified URL.
46    fn post(&mut self, url: impl AsRef<str>)
47    -> Result<&mut Self, ValidationError>;
48
49    /// Add a header to the request.
50    ///
51    /// To add a `User-Agent` header, call [user_agent](Self::user_agent).
52    fn with_header<S: AsRef<str>>(&mut self, name: S, value: S)
53    -> Result<&mut Self, ValidationError>;
54    /// Use the provided bytes as the request body.
55    fn with_body(&mut self, data: impl Into<Vec<u8>>) -> &mut Self;
56    /// Use the given [serde_json::Value] as the request's body.
57    fn with_body_json(&mut self, body: serde_json::Value) -> &mut Self;
58    /// Read the provided path as the request's body.
59    fn read_body_from_file(&mut self, path: impl Into<PathBuf>) -> &mut Self;
60
61    /// Set the User-Agent header value to send with requests.
62    fn user_agent(&mut self, user_agent_string: impl Into<String>)
63    -> Result<&mut Self, ValidationError>;
64
65    /// Send the previously-constructed request and return a response.
66    async fn send(&mut self) -> Result<Vec<u8>, Self::Error>;
67
68    /// Send the previously-constructed request and return a response with the
69    /// returned HTTP headers.
70    async fn send_keep_headers(&mut self)
71    -> Result<(Vec<u8>, HeaderMap), Self::Error>;
72}
73
74// TODO: Use http_types::{HeaderName, HeaderValue} instead of Strings?
75pub type HeaderMap = HashMap<String, String>;
76
77/// Generate a standard User-Agent string for HTTP client backends.
78///
79/// This is only useful if you either:
80///
81/// * create your own implementation of [HttpClient] and you want to maintain
82///   b2-client's standard User-Agent format, or
83/// * want to add contact information to the default User-Agent string.
84///
85/// # Examples
86///
87/// ```
88/// # use b2_client::client::default_user_agent;
89/// struct CurlClient { user_agent: String };
90///
91/// impl Default for CurlClient {
92///     fn default() -> Self {
93///         Self {
94///             user_agent: default_user_agent!("curl"),
95///         }
96///     }
97/// }
98/// ```
99///
100/// ```
101/// # #[cfg(feature = "with_surf")]
102/// # fn custom_user_agent() {
103/// use b2_client::client::{default_user_agent, HttpClient as _, SurfClient};
104///
105/// let client = SurfClient::default()
106///     .user_agent(default_user_agent!("surf (contact@example.com)"));
107/// # }
108///
109/// ```
110#[macro_export]
111macro_rules! default_user_agent {
112    ($client:literal) => {
113        format!("rust-b2-client/{}; {}",
114            option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"),
115            $client
116        )
117    };
118}
119pub use default_user_agent;
120
121#[cfg(feature = "with_surf")]
122mod surf_client {
123    use std::path::PathBuf;
124    use super::*;
125    use crate::error::Error;
126    use surf::{
127        http::Method,
128        Request,
129        Url,
130    };
131
132    #[derive(Debug, Clone)]
133    pub struct SurfClient {
134        client: surf::Client,
135        req: Option<Request>,
136        body: Option<Body>,
137        user_agent: String,
138    }
139
140    impl Default for SurfClient {
141        /// Create a new `SurfClient`.
142        fn default() -> Self {
143            Self {
144                client: surf::Client::new(),
145                req: None,
146                body: None,
147                user_agent: default_user_agent!("surf"),
148            }
149        }
150    }
151
152    // Body type for sending; TODO rename to avoid ambiguity?
153    #[derive(Debug, Clone)]
154    enum Body {
155        Json(serde_json::Value),
156        // TODO: I'd rather store a reference, but doing so spams lifetimes all
157        // over everything (if we can avoid adding an explicit lifetime to
158        // HttpClient we should be OK).
159        //
160        // The best solution is likely going to be to refcount it.
161        Bytes(Vec<u8>),
162        File(PathBuf),
163    }
164
165    impl SurfClient {
166        /// Use the provided [surf::Client] instead of a new one.
167        pub fn with_client(mut self, client: surf::Client) -> Self {
168            self.client = client;
169            self
170        }
171
172        async fn send_impl(&mut self, keep_headers: bool)
173        -> Result<(Vec<u8>, Option<HeaderMap>), <Self as HttpClient>::Error> {
174            if let Some(mut req) = self.req.to_owned() {
175                if let Some(body) = &self.body {
176                    match body {
177                        Body::Json(val) => req.body_json(val)?,
178                        Body::Bytes(data) => req.body_bytes(data),
179                        Body::File(path) =>
180                            req.set_body(surf::Body::from_file(path).await?),
181                    }
182                }
183
184                req.insert_header("User-Agent", &self.user_agent);
185
186                let mut res = self.client.send(req).await?;
187                let body = res.body_bytes().await?;
188
189                let headers = if keep_headers {
190                    let headers: &surf::http::Headers = res.as_ref();
191                    let mut ret = HeaderMap::new();
192
193                    for (k, v) in headers.iter() {
194                        ret.insert(k.to_string(), v.to_string());
195                    }
196
197                    Some(ret)
198                } else {
199                    None
200                };
201
202                self.req = None;
203
204                Ok((body, headers))
205            } else {
206                Err(Error::NoRequest)
207            }
208        }
209    }
210
211    macro_rules! gen_method_func {
212        ($func:ident, $method:ident) => {
213            fn $func(&mut self, url: impl AsRef<str>)
214            -> Result<&mut Self, ValidationError> {
215                let url = Url::parse(url.as_ref())?;
216                self.req = Some(Request::new(Method::$method, url));
217
218                Ok(self)
219            }
220        }
221    }
222
223    #[async_trait::async_trait]
224    impl HttpClient for SurfClient {
225        /// Errors that can be returned by a `SurfClient`.
226        type Error = Error<surf::Error>;
227
228        gen_method_func!(get, Get);
229        gen_method_func!(head, Head);
230        gen_method_func!(post, Post);
231
232        fn with_header<S: AsRef<str>>(&mut self, name: S, value: S)
233        -> Result<&mut Self, ValidationError> {
234            use std::str::FromStr as _;
235            use http_types::headers::{HeaderName, HeaderValue};
236
237            if let Some(req) = &mut self.req {
238                let name = HeaderName::from_str(name.as_ref())?;
239                let value = HeaderValue::from_str(value.as_ref())?;
240
241                req.insert_header(name, value);
242            }
243
244            Ok(self)
245        }
246
247        fn with_body(&mut self, data: impl Into<Vec<u8>>) -> &mut Self
248        {
249            self.body = Some(Body::Bytes(data.into()));
250            self
251        }
252
253        fn with_body_json(&mut self, body: serde_json::Value) -> &mut Self {
254            // Nothing I've tried will actually save the body in the req.
255            // We store it and set it in send().
256            self.body = Some(Body::Json(body));
257            self
258        }
259
260        fn read_body_from_file(&mut self, path: impl Into<PathBuf>)
261        -> &mut Self {
262            self.body = Some(Body::File(path.into()));
263            self
264        }
265
266        /// Set the User-Agent header value to send with requests.
267        ///
268        /// The default User-Agent string is "rust-b2-client/<version>; surf".
269        ///
270        /// # Errors
271        ///
272        /// Returns [ValidationError] if the `user_agent_string` is empty.
273        fn user_agent(&mut self, user_agent_string: impl Into<String>)
274        -> Result<&mut Self, ValidationError> {
275            let user_agent = user_agent_string.into();
276
277            if user_agent.is_empty() {
278                Err(ValidationError::MissingData(
279                    "User-Agent is required".into()
280                ))
281            } else {
282                self.user_agent = user_agent;
283                Ok(self)
284            }
285        }
286
287        /// Send the previously-constructed request and return a response.
288        ///
289        /// # Errors
290        ///
291        /// * If a request has not been created, returns [Error::NoRequest].
292        /// * Returns any underlying HTTP client errors in [Error::Client].
293        async fn send(&mut self) -> Result<Vec<u8>, Self::Error> {
294            self.send_impl(false).await.map(|v| v.0)
295        }
296
297        /// Send the previously-constructed request and return a response with
298        /// the returned HTTP headers.
299        ///
300        /// # Errors
301        ///
302        /// * If a request has not been created, returns [Error::NoRequest].
303        /// * Returns any underlying HTTP client errors in [Error::Client].
304        async fn send_keep_headers(&mut self)
305        -> Result<(Vec<u8>, HeaderMap), Self::Error> {
306            self.send_impl(true).await.map(|(r, m)| (r, m.unwrap()))
307        }
308    }
309}
310
311#[cfg(feature = "with_hyper")]
312mod hyper_client {
313    use std::path::PathBuf;
314    use super::*;
315    use crate::error::Error;
316    use hyper::{
317        client::connect::HttpConnector,
318        header::{HeaderName, HeaderValue},
319        Method
320    };
321    use hyper_tls::HttpsConnector;
322    use url::Url;
323
324
325    #[derive(Debug, Clone)]
326    pub struct HyperClient {
327        client: hyper::Client<HttpsConnector<HttpConnector>>,
328        method: Option<Method>,
329        url: String,
330        headers: Vec<(HeaderName, HeaderValue)>,
331        body: Option<Body>,
332        user_agent: String,
333    }
334
335    impl Default for HyperClient {
336        /// Create a new `HttpClient`.
337        fn default() -> Self {
338            let https = HttpsConnector::new();
339            let client = hyper::Client::builder()
340                .build::<_, hyper::Body>(https);
341
342            Self {
343                client,
344                method: None,
345                url: String::default(),
346                headers: vec![],
347                body: None,
348                user_agent: default_user_agent!("hyper"),
349            }
350        }
351    }
352
353    #[derive(Debug, Clone)]
354    enum Body {
355        Json(serde_json::Value),
356        Bytes(hyper::body::Bytes),
357        File(PathBuf),
358    }
359
360    macro_rules! gen_method_func {
361        ($func:ident, $method: ident) => {
362            fn $func(&mut self, url: impl AsRef<str>)
363            -> Result<&mut Self, ValidationError> {
364                let _url = Url::parse(url.as_ref())?;
365
366                self.method = Some(Method::$method);
367                self.url = String::from(url.as_ref());
368                Ok(self)
369            }
370        }
371    }
372
373    impl HyperClient {
374        pub fn with_client(
375            mut self,
376            client: hyper::Client<HttpsConnector<HttpConnector>>
377        ) -> Self {
378            self.client = client;
379            self
380        }
381
382        /// Use the provided [Bytes](hyper::body::Bytes) as the request's body.
383        ///
384        /// The `Bytes` type is cheaply cloneable, so this method should be
385        /// preferred over [with_bytes] when you wish to retain ownership of the
386        /// byte buffer (e.g., to reuse it when uploading multiple file parts).
387        pub fn with_body_bytes(&mut self, bytes: hyper::body::Bytes)
388        -> &mut Self {
389            self.body = Some(Body::Bytes(bytes));
390            self
391        }
392
393        async fn send_impl(&mut self, keep_headers: bool)
394        -> Result<(Vec<u8>, Option<HeaderMap>), <Self as HttpClient>::Error> {
395            if self.method.is_none() {
396                return Err(Error::NoRequest);
397            }
398
399            let mut req = hyper::Request::builder()
400                .method(self.method.as_ref().unwrap())
401                .uri(&self.url);
402
403            for (name, value) in &self.headers {
404                req = req.header(name, value);
405            }
406
407            req = req.header("User-Agent", &self.user_agent);
408
409            let body = match &self.body {
410                Some(body) => match body {
411                    Body::Json(val) => hyper::Body::from(val.to_string()),
412                    Body::Bytes(data) => hyper::Body::from(data.clone()),
413                    Body::File(path) => {
414                        use tokio::{
415                            fs::File,
416                            io::AsyncReadExt as _,
417                        };
418
419                        // TODO: We might do better to create a stream over the
420                        // file and pass it to the hyper::Body.
421                        let mut file = File::open(path).await?;
422                        let mut buf = vec![];
423                        file.read_to_end(&mut buf).await?;
424
425                        hyper::Body::from(buf)
426                    },
427                },
428                None => hyper::Body::empty(),
429            };
430
431            let req = req.body(body).expect(concat!(
432                "Invalid request. Please file an issue on b2-client for ",
433                "improper validation"
434            ));
435
436            let (mut parts, body) = self.client.request(req).await?
437                .into_parts();
438
439            let body = hyper::body::to_bytes(body).await?.to_vec();
440
441            let headers = if keep_headers {
442                let mut headers = HeaderMap::new();
443
444                headers.extend(
445                    parts.headers.drain()
446                        .filter(|(k, _)| k.is_some())
447                        .map(|(k, v)|
448                            // TODO: Ensure that all possible header values from
449                            // B2 are required to be valid strings on their
450                            // side.
451                            (
452                                k.unwrap().to_string(),
453                                v.to_str().unwrap().to_owned()
454                            )
455                        )
456                );
457
458                Some(headers)
459            } else {
460                None
461            };
462
463            self.method = None;
464            self.url = String::default();
465            self.headers.clear();
466            self.body = None;
467
468            Ok((body, headers))
469        }
470    }
471
472    #[async_trait::async_trait]
473    impl HttpClient for HyperClient {
474        type Error = Error<hyper::Error>;
475
476        gen_method_func!(get, GET);
477        gen_method_func!(head, HEAD);
478        gen_method_func!(post, POST);
479
480        /// Add a header to the request.
481        fn with_header<S: AsRef<str>>(&mut self, name: S, value: S)
482        -> Result<&mut Self, ValidationError> {
483            use std::str::FromStr as _;
484            use hyper::header::{HeaderName, HeaderValue};
485
486            let name = HeaderName::from_str(name.as_ref())?;
487            let value = HeaderValue::from_str(value.as_ref())?;
488
489            self.headers.push((name, value));
490            Ok(self)
491        }
492
493        fn with_body<'a>(&mut self, data: impl Into<Vec<u8>>) -> &mut Self {
494            self.body = Some(
495                Body::Bytes(hyper::body::Bytes::from(data.into()))
496            );
497
498            self
499        }
500
501        fn with_body_json(&mut self, body: serde_json::Value) -> &mut Self {
502            self.body = Some(Body::Json(body));
503            self
504        }
505
506        fn read_body_from_file(&mut self, path: impl Into<PathBuf>)
507        -> &mut Self {
508            self.body = Some(Body::File(path.into()));
509            self
510        }
511
512        fn user_agent(&mut self, user_agent_string: impl Into<String>)
513        -> Result<&mut Self, ValidationError> {
514            let user_agent = user_agent_string.into();
515
516            if user_agent.is_empty() {
517                Err(ValidationError::MissingData(
518                    "User-Agent is required".into()
519                ))
520            } else {
521                self.user_agent = user_agent;
522                Ok(self)
523            }
524        }
525
526        /// Send the previously-constructed request and return a response.
527        ///
528        /// # Errors
529        ///
530        /// * If a request has not been created, returns [Error::NoRequest].
531        /// * Returns any underlying HTTP client errors in [Error::Client].
532        async fn send(&mut self) -> Result<Vec<u8>, Self::Error> {
533            self.send_impl(false).await.map(|v| v.0)
534        }
535
536        async fn send_keep_headers(&mut self)
537        -> Result<(Vec<u8>, HeaderMap), Self::Error> {
538            self.send_impl(true).await.map(|(r, m)| (r, m.unwrap()))
539        }
540    }
541}
542
543#[cfg(feature = "with_isahc")]
544mod isahc_client {
545    use super::*;
546    use crate::error::Error;
547    use isahc::http::{
548        header::{HeaderName, HeaderValue},
549        method::Method,
550        request::Builder as RequestBuilder,
551    };
552
553    #[derive(Debug)]
554    enum Body {
555        Bytes(Vec<u8>),
556        Json(serde_json::Value),
557        File(PathBuf),
558    }
559
560    #[derive(Debug)]
561    pub struct IsahcClient {
562        client: isahc::HttpClient,
563        req: Option<RequestBuilder>,
564        user_agent: String,
565        body: Option<Body>,
566        headers: Vec<(HeaderName, HeaderValue)>,
567    }
568
569    impl Default for IsahcClient {
570        /// Create a new `HttpClient`.
571        fn default() -> Self {
572            Self {
573                client: isahc::HttpClient::new().unwrap(),
574                req: None,
575                user_agent: default_user_agent!("isahc"),
576                body: None,
577                headers: Vec::new(),
578            }
579        }
580    }
581
582    impl Clone for IsahcClient {
583        /// Clone an `IsahcClient` object.
584        ///
585        /// The client itself and the user-agent string are cloned. The current
586        /// request is not.
587        fn clone(&self) -> Self {
588            Self {
589                client: self.client.clone(),
590                req: None,
591                user_agent: self.user_agent.clone(),
592                body: None,
593                headers: Vec::new(),
594            }
595        }
596    }
597
598    impl IsahcClient {
599        async fn send_impl(&mut self, keep_headers: bool)
600        -> Result<(
601            Vec<u8>, Option<HeaderMap>),
602            <Self as super::HttpClient>::Error
603        > {
604            use futures_lite::AsyncReadExt as _;
605
606            if let Some(mut req) = self.req.take() {
607                for (name, value) in &self.headers {
608                    req = req.header(name, value);
609                }
610
611                req = req.header("User-Agent", &self.user_agent);
612
613                let body = if let Some(body) = self.body.take() {
614                    match body {
615                        Body::Bytes(bytes) => Some(bytes),
616                        Body::Json(json) => Some(serde_json::to_vec(&json)?),
617                        Body::File(path) => {
618                            // TODO: Use async_std?
619                            use std::{fs::File, io::Read as _};
620
621                            let mut file = File::open(path)?;
622                            let mut buf: Vec<u8> = vec![];
623                            file.read_to_end(&mut buf)?;
624
625                            Some(buf)
626                        },
627                    }
628                } else {
629                    None
630                };
631
632                let (mut parts, body) = match body {
633                    Some(body) => self.client.send_async(req.body(body)?)
634                        .await?.into_parts(),
635                    None => self.client.send_async(
636                        req.body(isahc::AsyncBody::empty())?
637                    ).await?.into_parts(),
638                };
639
640                let headers = if keep_headers {
641                    let mut headers = HeaderMap::new();
642
643                    headers.extend(
644                        parts.headers.drain()
645                        .filter(|(k, _)| k.is_some())
646                        .map(|(k, v)|
647                            // TODO: Ensure that all possible header values from
648                            // B2 are required to be valid strings on their
649                            // side.
650                            (
651                                k.unwrap().to_string(),
652                                v.to_str().unwrap().to_owned()
653                            )
654                        )
655                    );
656
657                    Some(headers)
658                } else {
659                    None
660                };
661
662                let mut buf = Vec::new();
663                body.bytes().read_to_end(&mut buf).await?;
664
665                // self.req and self.body had their values reset already; we
666                // only need to clear the list of headers and we're ready for
667                // the next request.
668                self.headers.clear();
669
670                Ok((buf, headers))
671            } else {
672                Err(Error::NoRequest)
673            }
674        }
675    }
676
677    macro_rules! gen_method_func {
678        ($func:ident, $method:ident) => {
679            fn $func(&mut self, url: impl AsRef<str>)
680            -> Result<&mut Self, ValidationError> {
681                self.req = Some(
682                    RequestBuilder::new()
683                        .method(Method::$method)
684                        .uri(url.as_ref())
685                );
686
687                Ok(self)
688            }
689        };
690    }
691
692    #[async_trait::async_trait]
693    impl HttpClient for IsahcClient
694        where Self: Clone + Sized,
695    {
696        type Error = Error<isahc::Error>;
697
698        gen_method_func!(get, GET);
699        gen_method_func!(head, HEAD);
700        gen_method_func!(post, POST);
701
702        fn with_header<S: AsRef<str>>(&mut self, name: S, value: S)
703        -> Result<&mut Self, ValidationError> {
704            use std::str::FromStr as _;
705
706            let name = HeaderName::from_str(name.as_ref())?;
707            let value = HeaderValue::from_str(value.as_ref())?;
708
709            self.headers.push((name, value));
710            Ok(self)
711        }
712
713        fn with_body(&mut self, data: impl Into<Vec<u8>>) -> &mut Self {
714            self.body = Some(Body::Bytes(data.into()));
715            self
716        }
717
718        fn with_body_json(&mut self, body: serde_json::Value) -> &mut Self {
719            self.body = Some(Body::Json(body));
720            self
721        }
722
723        fn read_body_from_file(&mut self, path: impl Into<PathBuf>)
724        -> &mut Self {
725            self.body = Some(Body::File(path.into()));
726            self
727        }
728
729        fn user_agent(&mut self, user_agent_string: impl Into<String>)
730        -> Result<&mut Self, ValidationError> {
731            let user_agent = user_agent_string.into();
732
733            if ! user_agent.is_empty() {
734                self.user_agent = user_agent;
735                Ok(self)
736            } else {
737                Err(ValidationError::MissingData(
738                    "User-Agent is required".into()
739                ))
740            }
741        }
742
743        async fn send(&mut self) -> Result<Vec<u8>, Self::Error> {
744            self.send_impl(false).await.map(|v| v.0)
745        }
746
747        async fn send_keep_headers(&mut self)
748        -> Result<(Vec<u8>, HeaderMap), Self::Error> {
749            self.send_impl(true).await.map(|v| (v.0, v.1.unwrap()))
750        }
751    }
752}