1use std::{mem, sync::Arc, time::Duration};
2
3use futures::future::BoxFuture;
4use util::maybe;
5
6pub use isahc::config::Configurable;
7pub struct IsahcHttpClient(isahc::HttpClient);
8
9pub use http_client::*;
10
11impl IsahcHttpClient {
12 pub fn new(proxy: Option<Uri>, user_agent: Option<String>) -> Arc<IsahcHttpClient> {
13 let mut builder = isahc::HttpClient::builder()
14 .connect_timeout(Duration::from_secs(5))
15 .low_speed_timeout(100, Duration::from_secs(5))
16 .proxy(proxy.clone());
17 if let Some(agent) = user_agent {
18 builder = builder.default_header("User-Agent", agent);
19 }
20 Arc::new(IsahcHttpClient(builder.build().unwrap()))
21 }
22 pub fn builder() -> isahc::HttpClientBuilder {
23 isahc::HttpClientBuilder::new()
24 }
25}
26
27impl From<isahc::HttpClient> for IsahcHttpClient {
28 fn from(client: isahc::HttpClient) -> Self {
29 Self(client)
30 }
31}
32
33impl HttpClient for IsahcHttpClient {
34 fn proxy(&self) -> Option<&Uri> {
35 None
36 }
37
38 fn send(
39 &self,
40 req: http_client::http::Request<http_client::AsyncBody>,
41 ) -> BoxFuture<'static, Result<http_client::Response<http_client::AsyncBody>, anyhow::Error>>
42 {
43 let redirect_policy = req
44 .extensions()
45 .get::<http_client::RedirectPolicy>()
46 .cloned()
47 .unwrap_or_default();
48 let read_timeout = req
49 .extensions()
50 .get::<http_client::ReadTimeout>()
51 .map(|t| t.0);
52 let req = maybe!({
53 let (mut parts, body) = req.into_parts();
54 let mut builder = isahc::Request::builder()
55 .method(parts.method)
56 .uri(parts.uri)
57 .version(parts.version);
58 if let Some(read_timeout) = read_timeout {
59 builder = builder.low_speed_timeout(100, read_timeout);
60 }
61
62 let headers = builder.headers_mut()?;
63 mem::swap(headers, &mut parts.headers);
64
65 let extensions = builder.extensions_mut()?;
66 mem::swap(extensions, &mut parts.extensions);
67
68 let isahc_body = match body.0 {
69 http_client::Inner::Empty => isahc::AsyncBody::empty(),
70 http_client::Inner::AsyncReader(reader) => isahc::AsyncBody::from_reader(reader),
71 http_client::Inner::SyncReader(reader) => {
72 isahc::AsyncBody::from_bytes_static(reader.into_inner())
73 }
74 };
75
76 builder
77 .redirect_policy(match redirect_policy {
78 http_client::RedirectPolicy::FollowAll => isahc::config::RedirectPolicy::Follow,
79 http_client::RedirectPolicy::FollowLimit(limit) => {
80 isahc::config::RedirectPolicy::Limit(limit)
81 }
82 http_client::RedirectPolicy::NoFollow => isahc::config::RedirectPolicy::None,
83 })
84 .body(isahc_body)
85 .ok()
86 });
87
88 let client = self.0.clone();
89
90 Box::pin(async move {
91 match req {
92 Some(req) => client
93 .send_async(req)
94 .await
95 .map_err(Into::into)
96 .map(|response| {
97 let (parts, body) = response.into_parts();
98 let body = http_client::AsyncBody::from_reader(body);
99 http_client::Response::from_parts(parts, body)
100 }),
101 None => Err(anyhow::anyhow!("Request was malformed")),
102 }
103 })
104 }
105}