Skip to main content

coman/core/
http_request.rs

1use std::time::Duration;
2
3use futures::StreamExt;
4use reqwest::multipart::Part;
5use reqwest::redirect::Policy;
6use reqwest::{multipart, ClientBuilder};
7
8use crate::core::errors::HttpError;
9use crate::core::http_client::{HttpMethod, HttpResult};
10use crate::core::http_response::HttpResponse;
11use crate::core::utils::build_header_map;
12
13/// HTTP Request Builder
14#[derive(Debug, Clone)]
15pub struct HttpRequest {
16    url: String,
17    method: HttpMethod,
18    headers: Vec<(String, String)>,
19    body: Option<String>,
20    body_bytes: Option<Vec<u8>>,
21    timeout: Option<Duration>,
22    follow_redirects: bool,
23}
24
25impl HttpRequest {
26    /// Create a new HTTP request
27    pub fn new(method: HttpMethod, url: &str) -> Self {
28        Self {
29            url: url.to_string(),
30            method,
31            headers: Vec::new(),
32            body: None,
33            body_bytes: None,
34            timeout: None,
35            follow_redirects: false,
36        }
37    }
38
39    /// Set request headers
40    pub fn headers(mut self, headers: Vec<(String, String)>) -> Self {
41        self.headers = headers;
42        self
43    }
44
45    /// Add a single header
46    pub fn header(mut self, key: &str, value: &str) -> Self {
47        self.headers.push((key.to_string(), value.to_string()));
48        self
49    }
50
51    /// Set request body as string
52    pub fn body(mut self, body: &str) -> Self {
53        self.body = Some(body.to_string());
54        self
55    }
56
57    /// Set request body as bytes
58    pub fn body_bytes(mut self, bytes: Vec<u8>) -> Self {
59        self.body_bytes = Some(bytes);
60        self
61    }
62
63    /// Set request timeout
64    pub fn timeout(mut self, timeout: Duration) -> Self {
65        self.timeout = Some(timeout);
66        self
67    }
68
69    /// Enable following redirects
70    pub fn follow_redirects(mut self, follow: bool) -> Self {
71        self.follow_redirects = follow;
72        self
73    }
74
75    /// Execute the request
76    pub async fn send(self) -> HttpResult<HttpResponse> {
77        let client_builder = ClientBuilder::new();
78
79        let client_builder = if self.follow_redirects {
80            client_builder.redirect(Policy::default())
81        } else {
82            client_builder.redirect(Policy::none())
83        };
84
85        let client_builder = if let Some(timeout) = self.timeout {
86            client_builder.timeout(timeout)
87        } else {
88            client_builder
89        };
90
91        let client = client_builder
92            .build()
93            .map_err(|e| HttpError::RequestError(e.to_string()))?;
94
95        let header_map = build_header_map(&self.headers);
96
97        let method = match self.method {
98            HttpMethod::Get => reqwest::Method::GET,
99            HttpMethod::Post => reqwest::Method::POST,
100            HttpMethod::Put => reqwest::Method::PUT,
101            HttpMethod::Delete => reqwest::Method::DELETE,
102            HttpMethod::Patch => reqwest::Method::PATCH,
103        };
104
105        let start = std::time::Instant::now();
106
107        let request_builder = client.request(method, &self.url).headers(header_map);
108
109        let request_builder = if let Some(bytes) = self.body_bytes {
110            request_builder.body(bytes)
111        } else if let Some(body) = self.body {
112            request_builder.body(body)
113        } else {
114            request_builder
115        };
116
117        let response = request_builder.send().await?;
118
119        let elapsed = start.elapsed().as_millis();
120        let status = response.status().as_u16();
121        let status_text = response.status().to_string();
122        let url = response.url().to_string();
123        let version = format!("{:?}", response.version());
124
125        let mut headers = Vec::new();
126        for (key, value) in response.headers().iter() {
127            if let Ok(v) = value.to_str() {
128                headers.push((key.to_string(), v.to_string()));
129            }
130        }
131
132        let body_bytes = response.bytes().await?.to_vec();
133        let body = String::from_utf8_lossy(&body_bytes).to_string();
134
135        Ok(HttpResponse {
136            version,
137            status,
138            status_text,
139            headers,
140            body,
141            elapsed_ms: elapsed,
142            url,
143        })
144    }
145
146    /// Execute the request and stream the response
147    pub async fn send_streaming<F>(self, mut on_chunk: F) -> HttpResult<HttpResponse>
148    where
149        F: FnMut(&[u8]) -> Result<(), Box<dyn std::error::Error>> + Send,
150    {
151        let client_builder = ClientBuilder::new();
152
153        let client_builder = if self.follow_redirects {
154            client_builder.redirect(Policy::default())
155        } else {
156            client_builder.redirect(Policy::none())
157        };
158
159        let client_builder = if let Some(timeout) = self.timeout {
160            client_builder.timeout(timeout)
161        } else {
162            client_builder
163        };
164
165        let client = client_builder
166            .build()
167            .map_err(|e| HttpError::RequestError(e.to_string()))?;
168
169        let header_map = build_header_map(&self.headers);
170
171        let method = match self.method {
172            HttpMethod::Get => reqwest::Method::GET,
173            HttpMethod::Post => reqwest::Method::POST,
174            HttpMethod::Put => reqwest::Method::PUT,
175            HttpMethod::Delete => reqwest::Method::DELETE,
176            HttpMethod::Patch => reqwest::Method::PATCH,
177        };
178
179        let start = std::time::Instant::now();
180
181        let request_builder = client.request(method, &self.url).headers(header_map);
182
183        let request_builder = if let Some(bytes) = self.body_bytes {
184            request_builder.body(bytes)
185        } else if let Some(body) = self.body {
186            request_builder.body(body)
187        } else {
188            request_builder
189        };
190
191        let response = request_builder.send().await?;
192
193        let status = response.status().as_u16();
194        let status_text = response.status().to_string();
195        let url = response.url().to_string();
196        let version = format!("{:?}", response.version());
197
198        let mut headers = Vec::new();
199        for (key, value) in response.headers().iter() {
200            if let Ok(v) = value.to_str() {
201                headers.push((key.to_string(), v.to_string()));
202            }
203        }
204
205        let mut stream = response.bytes_stream();
206
207        while let Some(chunk) = stream.next().await {
208            let chunk = chunk.map_err(|e| HttpError::ResponseError(e.to_string()))?;
209            on_chunk(&chunk).map_err(|e| HttpError::Other(e.to_string()))?;
210        }
211
212        let elapsed = start.elapsed().as_millis();
213
214        Ok(HttpResponse {
215            version,
216            status,
217            status_text,
218            headers,
219            body: String::new(),
220            elapsed_ms: elapsed,
221            url,
222        })
223    }
224
225    pub async fn send_multipart(self, part: Part) -> HttpResult<HttpResponse> {
226        let client_builder = ClientBuilder::new();
227
228        let client_builder = if self.follow_redirects {
229            client_builder.redirect(Policy::default())
230        } else {
231            client_builder.redirect(Policy::none())
232        };
233
234        let client_builder = if let Some(timeout) = self.timeout {
235            client_builder.timeout(timeout)
236        } else {
237            client_builder
238        };
239
240        let client = client_builder
241            .build()
242            .map_err(|e| HttpError::RequestError(e.to_string()))?;
243
244        let header_map = build_header_map(&self.headers);
245
246        let method = match self.method {
247            HttpMethod::Get => reqwest::Method::GET,
248            HttpMethod::Post => reqwest::Method::POST,
249            HttpMethod::Put => reqwest::Method::PUT,
250            HttpMethod::Delete => reqwest::Method::DELETE,
251            HttpMethod::Patch => reqwest::Method::PATCH,
252        };
253
254        let form = multipart::Form::new().part("file", part);
255
256        let start = std::time::Instant::now();
257
258        let response = client
259            .request(method, &self.url)
260            .headers(header_map)
261            .multipart(form)
262            .send()
263            .await?;
264
265        let elapsed = start.elapsed().as_millis();
266        let status = response.status().as_u16();
267        let status_text = response.status().to_string();
268        let url = response.url().to_string();
269        let version = format!("{:?}", response.version());
270
271        let mut headers = Vec::new();
272        for (key, value) in response.headers().iter() {
273            if let Ok(v) = value.to_str() {
274                headers.push((key.to_string(), v.to_string()));
275            }
276        }
277
278        let body_bytes = response.bytes().await?.to_vec();
279        let body = String::from_utf8_lossy(&body_bytes).to_string();
280
281        Ok(HttpResponse {
282            version,
283            status,
284            status_text,
285            headers,
286            body,
287            elapsed_ms: elapsed,
288            url,
289        })
290    }
291}