didbot_tls/
http_client.rs1use std::error::Error as StdError;
16use std::future::Future;
17use std::pin::Pin;
18
19use bytes::Bytes;
20use http::Request;
21use http_body_util::{BodyExt, Full};
22use instant_acme::{BodyWrapper, BytesResponse, Error as AcmeError, HttpClient};
23
24pub struct ReqwestAcmeClient(reqwest::Client);
26
27impl ReqwestAcmeClient {
28 pub fn new() -> Self {
32 Self(didbot_http::client())
33 }
34}
35
36impl Default for ReqwestAcmeClient {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42fn other(err: impl StdError + Send + Sync + 'static) -> AcmeError {
43 AcmeError::Other(Box::new(err))
44}
45
46impl HttpClient for ReqwestAcmeClient {
47 fn request(
48 &self,
49 req: Request<BodyWrapper<Bytes>>,
50 ) -> Pin<Box<dyn Future<Output = Result<BytesResponse, AcmeError>> + Send>> {
51 let client = self.0.clone();
52 Box::pin(async move {
53 let (parts, body) = req.into_parts();
54 let body_bytes = body
58 .collect()
59 .await
60 .expect("BodyWrapper<Bytes>'s body collection cannot fail: its Error is Infallible")
61 .to_bytes();
62
63 let url: reqwest::Url = parts
64 .uri
65 .to_string()
66 .parse()
67 .map_err(|err: url::ParseError| other(err))?;
68
69 let mut builder = client.request(parts.method, url);
70 builder = builder.headers(parts.headers);
71 if !body_bytes.is_empty() {
72 builder = builder.body(body_bytes);
73 }
74
75 let response = builder.send().await.map_err(other)?;
76 let status = response.status();
77 let headers = response.headers().clone();
78 let response_bytes = Bytes::from(
79 didbot_http::read_bounded(response, didbot_http::MAX_JSON_BODY)
80 .await
81 .map_err(other)?,
82 );
83
84 let mut http_response = http::Response::builder().status(status);
85 if let Some(response_headers) = http_response.headers_mut() {
86 *response_headers = headers;
87 }
88 let http_response = http_response
89 .body(Full::new(response_bytes))
90 .map_err(other)?;
91
92 Ok(BytesResponse::from(http_response))
93 })
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use instant_acme::{Account, NewAccount};
100
101 use super::*;
102
103 #[tokio::test(start_paused = true)]
107 async fn a_directory_that_never_answers_fails_after_the_client_timeout() {
108 let addr = didbot_http::test_support::hung_server();
109 let started = tokio::time::Instant::now();
110 let registering = Account::builder_with_http(Box::new(ReqwestAcmeClient::new())).create(
111 &NewAccount {
112 contact: &[],
113 terms_of_service_agreed: true,
114 only_return_existing: false,
115 },
116 format!("http://{addr}/directory"),
117 None,
118 );
119 let result = tokio::time::timeout(didbot_http::DEFAULT_TIMEOUT * 2, registering)
120 .await
121 .expect("the client's own timeout fires first");
122 assert!(result.is_err(), "nothing ever answers");
123 assert_eq!(started.elapsed(), didbot_http::DEFAULT_TIMEOUT);
124 }
125}