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