1use std::{future::Future, time::Duration};
2
3use anyhow::{Context, Result};
4#[cfg(feature = "instrumentation")]
5use axum::http;
6use bytes::Buf;
7use http_body_util::BodyExt;
8use hyper::{body::Incoming, client::conn::http1, Method, Request, Response, Uri};
9use hyper_util::rt::TokioIo;
10use serde::{de::DeserializeOwned, Serialize};
11use tracing::{error, warn};
12
13pub use hyper::http::StatusCode;
14
15pub enum ContentType {
16 Text,
17 Json,
18 OctetStream,
19}
20
21#[derive(Clone)]
22pub struct HttpClient {
23 pub url: Uri,
24 pub api_key: Option<String>,
25 pub retry: Option<(usize, Duration)>,
26}
27impl HttpClient {
28 #[cfg_attr(
29 feature = "instrumentation",
30 tracing::instrument(skip(self, body, content_type))
31 )]
32 pub async fn request<T>(
33 &self,
34 endpoint: &str,
35 method: Method,
36 content_type: ContentType,
37 body: Option<&T>,
38 ) -> anyhow::Result<Response<Incoming>>
39 where
40 T: Serialize,
41 {
42 let full_url = format!("{}{}", &self.url, endpoint);
43 let uri: Uri = full_url.parse().context("Parsing URI")?;
44
45 let authority = uri.authority().context("URI Authority")?.clone();
46 let host = authority.host().to_string();
47 let port = authority.port_u16().unwrap_or(80);
48 let addr = format!("{host}:{port}");
49 let stream = crate::net::TcpStream::connect(addr)
50 .await
51 .context("TCP connection")?;
52 let io = TokioIo::new(stream);
53
54 let (mut sender, connection) = http1::handshake(io).await.context("Handshake HTTP/1")?;
55
56 tokio::spawn(async move {
57 if let Err(e) = connection.await {
58 error!("Connection error {:?}", e);
59 }
60 });
61
62 let content_type_header = match content_type {
63 ContentType::Text => "application/text",
64 ContentType::Json => "application/json",
65 ContentType::OctetStream => "application/octet-stream",
66 };
67
68 let mut req_builder = Request::builder()
69 .method(method)
70 .uri(uri)
71 .header(hyper::header::CONTENT_TYPE, content_type_header);
72
73 if let Some(ref key) = self.api_key {
74 req_builder = req_builder.header("X-API-KEY", key);
75 }
76
77 #[cfg(feature = "instrumentation")]
78 if let Some(headers) = req_builder.headers_mut() {
79 opentelemetry::global::get_text_map_propagator(|propagator| {
80 use tracing_opentelemetry::OpenTelemetrySpanExt;
81 let context = tracing::Span::current().context();
82 propagator.inject_context(&context, &mut HeaderMapInjector(headers))
83 });
84 }
85
86 let request = if let Some(b) = body {
87 let json_body = serde_json::to_string(b).context("Serializing request body")?;
88
89 req_builder.body(json_body).context("Building request")?
90 } else {
91 req_builder.body("".to_string())?
92 };
93
94 let response = sender
95 .send_request(request)
96 .await
97 .context("Sending request")?;
98
99 if !response.status().is_success() {
100 let body = Self::parse_response_text(response).await?;
101 anyhow::bail!(body);
102 }
103
104 Ok(response)
105 }
106
107 async fn parse_response_text(response: Response<Incoming>) -> anyhow::Result<String> {
108 let str_bytes = Self::parse_response_bytes(response).await?;
109 let str = String::from_utf8(str_bytes.to_vec())?;
110
111 Ok(str)
112 }
113
114 async fn parse_response_bytes(response: Response<Incoming>) -> anyhow::Result<bytes::Bytes> {
115 let body = response.into_body();
116 let response = body.collect().await.context("Collecting body bytes")?;
117 Ok(response.to_bytes())
118 }
119
120 async fn parse_response_json<T: serde::de::DeserializeOwned>(
121 response: Response<Incoming>,
122 ) -> anyhow::Result<T> {
123 let body = response.into_body();
124
125 let response = body
126 .collect()
127 .await
128 .context("Collecting body bytes")?
129 .aggregate();
130
131 let result =
132 serde_json::from_reader(response.reader()).context("Deserializing response body")?;
133 Ok(result)
134 }
135
136 async fn retry<F, Fut, R>(&self, do_request: F) -> Result<R>
137 where
138 F: Fn() -> Fut,
139 Fut: Future<Output = anyhow::Result<R>>,
140 {
141 match self.retry {
142 Some((n, duration)) => {
143 let mut inner_n = n;
144
145 loop {
146 match do_request().await {
147 Ok(res) => break Ok(res),
148 Err(e) if inner_n > 0 => {
149 warn!(
150 "Error when doing request, waiting {} millis before retrying: {}",
151 duration.as_millis(),
152 e
153 );
154 inner_n -= 1;
155 tokio::time::sleep(duration).await;
156 }
157 Err(e) => {
158 break Err(e).context("Client errored after {} retries, stopping now.");
160 }
161 }
162 }
163 }
164 None => do_request().await,
165 }
166 }
167
168 pub async fn get<R>(&self, endpoint: &str) -> anyhow::Result<R>
169 where
170 R: DeserializeOwned,
171 {
172 let do_request = async || {
173 self.request::<String>(endpoint, Method::GET, ContentType::Json, None)
174 .await
175 };
176 let response = self.retry(do_request).await?;
177 Self::parse_response_json(response).await
178 }
179
180 pub async fn get_str(&self, endpoint: &str) -> anyhow::Result<String> {
181 let do_request = async || {
182 self.request::<String>(endpoint, Method::GET, ContentType::OctetStream, None)
183 .await
184 };
185 let response = self.retry(do_request).await?;
186 Self::parse_response_text(response).await
187 }
188
189 pub async fn get_bytes(&self, endpoint: &str) -> anyhow::Result<bytes::Bytes> {
190 let do_request = async || {
191 self.request::<String>(endpoint, Method::GET, ContentType::Text, None)
192 .await
193 };
194 let response = self.retry(do_request).await?;
195 Self::parse_response_bytes(response).await
196 }
197
198 pub async fn post_json<T, R>(&self, endpoint: &str, body: &T) -> anyhow::Result<R>
199 where
200 R: DeserializeOwned,
201 T: Serialize,
202 {
203 let do_request = async || {
204 self.request::<T>(endpoint, Method::POST, ContentType::Json, Some(body))
205 .await
206 };
207 let response = self.retry(do_request).await?;
208 Self::parse_response_json(response).await
209 }
210}
211
212#[cfg(feature = "instrumentation")]
213struct HeaderMapInjector<'a>(&'a mut http::HeaderMap);
214
215#[cfg(feature = "instrumentation")]
216impl opentelemetry::propagation::Injector for HeaderMapInjector<'_> {
217 fn set(&mut self, key: &str, value: String) {
218 use axum::http::{HeaderName, HeaderValue};
219
220 match HeaderName::from_bytes(key.as_bytes()) {
221 Ok(key) => match HeaderValue::try_from(&value) {
222 Ok(value) => {
223 self.0.insert(key, value);
224 }
225
226 Err(error) => warn!(value, error =? error, "parse metadata value"),
227 },
228
229 Err(error) => warn!(key, error =? error, "parse metadata key"),
230 }
231 }
232}