1use std::time::Duration;
2
3use async_trait::async_trait;
4use bytes::Bytes;
5use futures_util::stream;
6use reqwest::redirect::Policy;
7
8use crate::client::DEFAULT_TIMEOUT;
9use crate::error::Error;
10use crate::http::{Body, HttpClient, Request, Response};
11
12#[derive(Debug, Clone)]
17pub struct ReqwestClient {
18 http: reqwest::Client,
19}
20
21impl ReqwestClient {
22 pub fn with_timeout(timeout: Duration) -> Result<ReqwestClient, Error> {
24 ReqwestClient::from_builder(reqwest::Client::builder().timeout(timeout))
25 }
26
27 pub fn from_builder(builder: reqwest::ClientBuilder) -> Result<ReqwestClient, Error> {
31 let http = builder
32 .redirect(Policy::none())
33 .build()
34 .map_err(|error| Error::usage(format!("HTTP client: {error}")))?;
35 Ok(ReqwestClient { http })
36 }
37}
38
39impl Default for ReqwestClient {
47 #[allow(clippy::expect_used)] fn default() -> ReqwestClient {
49 ReqwestClient::with_timeout(DEFAULT_TIMEOUT)
50 .expect("reqwest builds a client from its defaults")
51 }
52}
53
54#[async_trait]
55impl HttpClient for ReqwestClient {
56 async fn send(&self, request: Request<Bytes>) -> Result<Response<Body>, Error> {
57 let request = reqwest::Request::try_from(request).map_err(network)?;
58 let answered = self.http.execute(request).await.map_err(network)?;
59
60 let status = answered.status();
61 let version = answered.version();
62 let headers = answered.headers().clone();
63 let content_length = answered.content_length();
64
65 let mut response = Response::new(Body::from_stream(chunks(answered), content_length));
66 *response.status_mut() = status;
67 *response.version_mut() = version;
68 *response.headers_mut() = headers;
69 Ok(response)
70 }
71}
72
73fn chunks(response: reqwest::Response) -> impl stream::Stream<Item = Result<Bytes, Error>> + Send {
76 stream::try_unfold(response, |mut response| async move {
77 match response.chunk().await {
78 Ok(Some(chunk)) => Ok(Some((chunk, response))),
79 Ok(None) => Ok(None),
80 Err(error) => Err(network(error)),
81 }
82 })
83}
84
85fn network(error: reqwest::Error) -> Error {
88 Error::network(error.without_url())
89}