coman/core/
http_request.rs

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