1use std::time::Duration;
2
3use bytes::Bytes;
4use http::{HeaderMap, Method, StatusCode};
5use url::Url;
6
7use crate::error::{Error, TransportError};
8
9#[derive(Debug)]
10pub(crate) struct HttpTransport {
11 client: reqwest::Client,
12}
13
14pub(crate) struct RawResponse {
15 pub status: StatusCode,
16 pub headers: HeaderMap,
17 pub body: Bytes,
18 pub attempts: u32,
19}
20
21impl HttpTransport {
22 pub(crate) fn new() -> Result<Self, Error> {
23 let client = reqwest::Client::builder()
24 .tcp_nodelay(true)
25 .pool_idle_timeout(Duration::from_secs(90))
26 .http2_keep_alive_interval(Duration::from_secs(30))
27 .http2_keep_alive_timeout(Duration::from_secs(10))
28 .http2_keep_alive_while_idle(true)
29 .http2_adaptive_window(true)
30 .build()
31 .map_err(|err| Error::InvalidRequest(format!("failed to build HTTP client: {err}")))?;
32 Ok(Self { client })
33 }
34
35 pub(crate) async fn send(
36 &self,
37 method: Method,
38 url: Url,
39 headers: HeaderMap,
40 body: Option<Bytes>,
41 timeout: Duration,
42 ) -> Result<RawResponse, Error> {
43 let mut request = self
44 .client
45 .request(method, url)
46 .headers(headers)
47 .timeout(timeout);
48 if let Some(body) = body {
49 request = request.body(body);
50 }
51 let response = match request.send().await {
52 Ok(response) => response,
53 Err(err) => return Err(classify_reqwest(err, timeout)),
54 };
55 let status = response.status();
56 let headers = response.headers().clone();
57 let body = match response.bytes().await {
58 Ok(body) => body,
59 Err(err) => return Err(classify_reqwest(err, timeout)),
60 };
61 Ok(RawResponse {
62 status,
63 headers,
64 body,
65 attempts: 1,
66 })
67 }
68}
69
70fn classify_reqwest(err: reqwest::Error, timeout: Duration) -> Error {
71 if err.is_timeout() {
72 Error::Timeout { after: timeout }
73 } else {
74 Error::Connection(TransportError::from_reqwest(&err))
75 }
76}
77
78pub(crate) fn join_endpoint(base: &Url, path: &str) -> Result<Url, Error> {
79 let mut root = base.as_str().trim_end_matches('/').to_owned();
80 root.push_str(path);
81 Url::parse(&root).map_err(|err| Error::InvalidRequest(format!("invalid endpoint URL: {err}")))
82}