coman/core/
http_request.rs1use 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#[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 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 pub fn headers(mut self, headers: Vec<(String, String)>) -> Self {
42 self.headers = headers;
43 self
44 }
45
46 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 pub fn body(mut self, body: &str) -> Self {
54 self.body = Some(body.to_string());
55 self
56 }
57
58 pub fn body_bytes(mut self, bytes: Vec<u8>) -> Self {
60 self.body_bytes = Some(bytes);
61 self
62 }
63
64 pub fn timeout(mut self, timeout: Duration) -> Self {
66 self.timeout = Some(timeout);
67 self
68 }
69
70 pub fn follow_redirects(mut self, follow: bool) -> Self {
72 self.follow_redirects = follow;
73 self
74 }
75
76 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 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}