azure_core/http_client/
reqwest.rs

1use crate::{
2    error::{ErrorKind, ResultExt},
3    Body, HttpClient, PinnedStream,
4};
5use async_trait::async_trait;
6use futures::TryStreamExt;
7use std::{collections::HashMap, str::FromStr, sync::Arc};
8use tracing::{debug, warn};
9
10/// Construct a new `HttpClient` with the `reqwest` backend.
11pub fn new_reqwest_client() -> Arc<dyn HttpClient> {
12    debug!("instantiating an http client using the reqwest backend");
13
14    // set `pool_max_idle_per_host` to `0` to avoid an issue in the underlying
15    // `hyper` library that causes the `reqwest` client to hang in some cases.
16    //
17    // See <https://github.com/hyperium/hyper/issues/2312> for more details.
18    #[cfg(not(target_arch = "wasm32"))]
19    let client = ::reqwest::ClientBuilder::new()
20        .pool_max_idle_per_host(0)
21        .build()
22        .expect("failed to build `reqwest` client");
23
24    // `reqwest` does not implement `pool_max_idle_per_host()` on WASM.
25    #[cfg(target_arch = "wasm32")]
26    let client = ::reqwest::ClientBuilder::new()
27        .build()
28        .expect("failed to build `reqwest` client");
29
30    Arc::new(client)
31}
32
33#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
34#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
35impl HttpClient for ::reqwest::Client {
36    async fn execute_request(&self, request: &crate::Request) -> crate::Result<crate::Response> {
37        let url = request.url().clone();
38        let method = request.method();
39        let mut req = self.request(try_from_method(*method)?, url.clone());
40        for (name, value) in request.headers().iter() {
41            req = req.header(name.as_str(), value.as_str());
42        }
43        let body = request.body().clone();
44
45        let reqwest_request = match body {
46            Body::Bytes(bytes) => req.body(bytes).build(),
47
48            // We cannot currently implement `Body::SeekableStream` for WASM
49            // because `reqwest::Body::wrap_stream()` is not implemented for WASM.
50            #[cfg(not(target_arch = "wasm32"))]
51            Body::SeekableStream(seekable_stream) => req
52                .body(::reqwest::Body::wrap_stream(seekable_stream))
53                .build(),
54        }
55        .context(ErrorKind::Other, "failed to build `reqwest` request")?;
56
57        debug!("performing request {method} '{url}' with `reqwest`");
58        let rsp = self
59            .execute(reqwest_request)
60            .await
61            .context(ErrorKind::Io, "failed to execute `reqwest` request")?;
62
63        let status = rsp.status();
64        let headers = to_headers(rsp.headers());
65
66        let body: PinnedStream = Box::pin(rsp.bytes_stream().map_err(|error| {
67            crate::error::Error::full(
68                ErrorKind::Io,
69                error,
70                "error converting `reqwest` request into a byte stream",
71            )
72        }));
73
74        Ok(crate::Response::new(
75            try_from_status(status)?,
76            headers,
77            body,
78        ))
79    }
80}
81
82fn to_headers(map: &::reqwest::header::HeaderMap) -> crate::headers::Headers {
83    let map = map
84        .iter()
85        .filter_map(|(k, v)| {
86            let key = k.as_str();
87            if let Ok(value) = v.to_str() {
88                Some((
89                    crate::headers::HeaderName::from(key.to_owned()),
90                    crate::headers::HeaderValue::from(value.to_owned()),
91                ))
92            } else {
93                warn!("header value for `{key}` is not utf8");
94                None
95            }
96        })
97        .collect::<HashMap<_, _>>();
98    crate::headers::Headers::from(map)
99}
100
101fn try_from_method(method: crate::Method) -> crate::Result<::reqwest::Method> {
102    match method {
103        crate::Method::Connect => Ok(::reqwest::Method::CONNECT),
104        crate::Method::Delete => Ok(::reqwest::Method::DELETE),
105        crate::Method::Get => Ok(::reqwest::Method::GET),
106        crate::Method::Head => Ok(::reqwest::Method::HEAD),
107        crate::Method::Options => Ok(::reqwest::Method::OPTIONS),
108        crate::Method::Patch => Ok(::reqwest::Method::PATCH),
109        crate::Method::Post => Ok(::reqwest::Method::POST),
110        crate::Method::Put => Ok(::reqwest::Method::PUT),
111        crate::Method::Trace => Ok(::reqwest::Method::TRACE),
112        _ => ::reqwest::Method::from_str(method.as_ref()).map_kind(ErrorKind::DataConversion),
113    }
114}
115
116fn try_from_status(status: ::reqwest::StatusCode) -> crate::Result<crate::StatusCode> {
117    let status = u16::from(status);
118    crate::StatusCode::try_from(status).map_err(|_| {
119        crate::error::Error::with_message(ErrorKind::DataConversion, || {
120            format!("invalid status code {status}")
121        })
122    })
123}