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
10pub fn new_reqwest_client() -> Arc<dyn HttpClient> {
12 debug!("instantiating an http client using the reqwest backend");
13
14 #[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 #[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 #[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}