origin_http_reqwest/
lib.rs1use async_trait::async_trait;
7use origin_domain::{AppError, Result};
8use origin_http::{Headers, HttpClient, HttpRequest, HttpResponse};
9use std::time::Duration;
10
11const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
14
15const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
18
19const DEFAULT_MAX_RESPONSE_BYTES: u64 = 10 * 1024 * 1024;
23
24#[derive(Debug, Clone)]
25pub struct ReqwestHttpClient {
26 inner: reqwest::Client,
27 max_response_bytes: u64,
28}
29
30impl ReqwestHttpClient {
31 pub fn new(user_agent: impl AsRef<str>) -> Result<Self> {
36 Self::builder(user_agent).build()
37 }
38
39 pub fn builder(user_agent: impl AsRef<str>) -> ReqwestHttpClientBuilder {
40 ReqwestHttpClientBuilder {
41 user_agent: user_agent.as_ref().to_owned(),
42 timeout: DEFAULT_TIMEOUT,
43 connect_timeout: DEFAULT_CONNECT_TIMEOUT,
44 max_response_bytes: DEFAULT_MAX_RESPONSE_BYTES,
45 }
46 }
47}
48
49#[derive(Debug, Clone)]
50pub struct ReqwestHttpClientBuilder {
51 user_agent: String,
52 timeout: Duration,
53 connect_timeout: Duration,
54 max_response_bytes: u64,
55}
56
57impl ReqwestHttpClientBuilder {
58 pub fn timeout(mut self, timeout: Duration) -> Self {
59 self.timeout = timeout;
60 self
61 }
62
63 pub fn connect_timeout(mut self, connect_timeout: Duration) -> Self {
64 self.connect_timeout = connect_timeout;
65 self
66 }
67
68 pub fn max_response_bytes(mut self, max_response_bytes: u64) -> Self {
72 self.max_response_bytes = max_response_bytes;
73 self
74 }
75
76 pub fn build(self) -> Result<ReqwestHttpClient> {
77 let inner = reqwest::Client::builder()
78 .user_agent(self.user_agent)
79 .timeout(self.timeout)
80 .connect_timeout(self.connect_timeout)
81 .build()
82 .map_err(|error| {
83 AppError::configuration(format!("cannot build http client: {error}"))
84 })?;
85
86 Ok(ReqwestHttpClient {
87 inner,
88 max_response_bytes: self.max_response_bytes,
89 })
90 }
91}
92
93#[async_trait]
94impl HttpClient for ReqwestHttpClient {
95 async fn send(&self, request: HttpRequest) -> Result<HttpResponse> {
96 let method = reqwest::Method::from_bytes(request.method.as_str().as_bytes())
97 .map_err(|error| AppError::internal(format!("invalid http method: {error}")))?;
98
99 tracing::debug!(
103 method = %request.method,
104 url = %request.url.split('?').next().unwrap_or(&request.url),
105 "http request"
106 );
107
108 let mut builder = self.inner.request(method, &request.url);
109 for (name, value) in request.headers.iter() {
110 builder = builder.header(name, value);
111 }
112 if let Some(body) = request.body {
113 builder = builder.body(body);
114 }
115
116 let response = builder.send().await.map_err(to_app_error)?;
117
118 let status = response.status().as_u16();
119 let headers = response
120 .headers()
121 .iter()
122 .filter_map(|(name, value)| {
123 value
126 .to_str()
127 .ok()
128 .map(|value| (name.as_str().to_owned(), value.to_owned()))
129 })
130 .collect::<Headers>();
131
132 let body = read_body_limited(response, self.max_response_bytes).await?;
133
134 tracing::debug!(status, bytes = body.len(), "http response");
135 Ok(HttpResponse::new(status, headers, body))
136 }
137}
138
139async fn read_body_limited(mut response: reqwest::Response, limit: u64) -> Result<Vec<u8>> {
145 if let Some(length) = response.content_length()
146 && length > limit
147 {
148 return Err(AppError::ExternalService(format!(
149 "response declared {length} bytes, over the {limit} byte limit"
150 )));
151 }
152
153 let mut body = Vec::new();
154 while let Some(chunk) = response.chunk().await.map_err(to_app_error)? {
155 if body.len() as u64 + chunk.len() as u64 > limit {
156 return Err(AppError::ExternalService(format!(
157 "response body exceeds the {limit} byte limit"
158 )));
159 }
160 body.extend_from_slice(&chunk);
161 }
162 Ok(body)
163}
164
165fn to_app_error(error: reqwest::Error) -> AppError {
170 if error.is_timeout() {
171 return AppError::Network(format!("request timed out: {error}"));
172 }
173
174 if error.is_connect() {
175 return AppError::Offline(format!("cannot reach host: {error}"));
176 }
177
178 AppError::Network(error.to_string())
179}