Skip to content

Commit a50bd18

Browse files
committed
feat(request): introduce structured request builder and improve UI
Add dedicated Request structure for client configuration and templating: - Create Request struct to scaffold HTTP client configuration - Move client builder from CLI to Request module for better separation - Improve status code visualization with consistent ordering - Update statistics labels for clarity - Reposition URL in request layout for better readability
1 parent b6c27cf commit a50bd18

File tree

5 files changed

+192
-94
lines changed

5 files changed

+192
-94
lines changed

install.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ elif [ "$(uname -s)" = "Linux" ]; then
4949
echo "ID: $ID"
5050
[ -n "$ID_LIKE" ] && echo "ID_LIKE: $ID_LIKE"
5151

52-
TEMP_DIR=/var/folders/dd/6w95nrsn6jn2qd5w439kzdzr0000gn/T/tmp.VwMa2wkGff
52+
TEMP_DIR=/var/folders/dd/6w95nrsn6jn2qd5w439kzdzr0000gn/T/tmp.Bjv8tI4Qmq
5353
cd ""
5454

5555
echo "Downloading binary..."

src/cli.rs

Lines changed: 19 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use clap::{ArgAction::HelpLong, Error, Parser};
22
use curl_parser;
3-
use reqwest::{header::USER_AGENT, Proxy};
3+
use reqwest::Proxy;
44
use serde::Deserialize;
55

6+
use crate::request::{Request, RequestSettings};
67
use crate::utils::{default_user_agent, num_of_cores, version};
7-
use crate::PepeError;
88

99
const BBLUE: &str = "\x1b[1;34m"; // Bold Blue
1010
const BGREEN: &str = "\x1b[1;32m"; // Bold Green
@@ -193,59 +193,25 @@ impl Cli {
193193
Ok(())
194194
}
195195

196-
pub fn build_client(&self) -> Result<reqwest::Client, PepeError> {
197-
let mut request_headers = reqwest::header::HeaderMap::new();
198-
199-
// Add user agent
200-
request_headers.insert(
201-
USER_AGENT,
202-
self.user_agent
203-
.parse::<reqwest::header::HeaderValue>()
204-
.map_err(|e| PepeError::HeaderParseError(e.to_string()))?,
205-
);
206-
207-
// Parse headers safely
208-
for header in &self.headers {
209-
let parts: Vec<&str> = header.splitn(2, ':').collect();
210-
if parts.len() == 2 {
211-
let name = reqwest::header::HeaderName::from_bytes(parts[0].trim().as_bytes())
212-
.map_err(|e| PepeError::HeaderParseError(e.to_string()))?;
213-
let value = reqwest::header::HeaderValue::from_str(parts[1].trim())
214-
.map_err(|e| PepeError::HeaderParseError(e.to_string()))?;
215-
request_headers.insert(name, value);
216-
}
217-
}
218-
219-
let mut client_builder = reqwest::Client::builder()
220-
.default_headers(request_headers)
221-
.timeout(std::time::Duration::from_secs(self.timeout as u64));
222-
223-
if let Some(proxy_url) = &self.proxy {
224-
let proxy =
225-
Proxy::all(proxy_url).map_err(|e| PepeError::HeaderParseError(e.to_string()))?;
226-
client_builder = client_builder.proxy(proxy);
196+
pub fn settings(&self) -> RequestSettings {
197+
RequestSettings {
198+
user_agent: self.user_agent.clone(),
199+
timeout: self.timeout,
200+
proxy: self.proxy.clone(),
201+
disable_compression: self.disable_compression,
202+
disable_keepalive: self.disable_keepalive,
203+
disable_redirects: self.disable_redirects,
227204
}
205+
}
228206

229-
if self.disable_compression {
230-
client_builder = client_builder.no_gzip();
231-
}
232-
233-
if self.disable_keepalive {
234-
client_builder = client_builder.connection_verbose(true);
235-
}
236-
237-
if self.disable_redirects {
238-
client_builder = client_builder.redirect(reqwest::redirect::Policy::none());
239-
}
240-
241-
client_builder =
242-
client_builder.timeout(std::time::Duration::from_secs(self.timeout as u64));
243-
244-
let client: reqwest::Client = client_builder
245-
.build()
246-
.map_err(|e| PepeError::RequestError(e))?;
247-
248-
Ok(client)
207+
pub fn request(&self) -> Request {
208+
Request::new(
209+
self.url.clone(),
210+
self.method.clone(),
211+
self.body.clone(),
212+
self.headers.clone(),
213+
self.settings(),
214+
)
249215
}
250216

251217
pub async fn check_for_updates(&self) -> Result<(), Box<dyn std::error::Error>> {

src/main.rs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ use crossterm::{
1010
};
1111
use tokio::sync::{mpsc, Semaphore};
1212

13+
use crate::cli::Cli;
14+
use crate::request::Request;
1315
use crate::response::ResponseStats;
1416
use crate::utils::resolve_dns;
15-
use cli::Cli;
1617

1718
mod cache;
1819
mod cli;
20+
mod request;
1921
mod response;
2022
mod ui;
2123
mod utils;
@@ -50,28 +52,26 @@ impl std::error::Error for PepeError {}
5052

5153
async fn handle_request(
5254
client: Arc<reqwest::Client>,
53-
url: String,
54-
method: String,
55-
body: Option<String>,
55+
request: Request,
5656
tx: mpsc::Sender<ResponseStats>,
5757
sent_tx: mpsc::Sender<Sent>,
5858
permit: tokio::sync::OwnedSemaphorePermit,
5959
) {
6060
let start = std::time::Instant::now();
61-
let method = reqwest::Method::from_bytes(method.as_bytes()).unwrap_or(reqwest::Method::GET);
61+
let method = request.method();
6262

6363
let _ = sent_tx.send(Sent { count: 1 }).await;
6464

65-
let dns_times = resolve_dns(&url).await.unwrap_or_default();
65+
let dns_times = resolve_dns(&request.url).await.unwrap_or_default();
6666

67-
let response = if method == reqwest::Method::POST && body.is_some() {
67+
let response = if method == reqwest::Method::POST && request.body.is_some() {
6868
client
69-
.request(method, &url)
70-
.body(body.unwrap())
69+
.request(method, &request.url)
70+
.body(request.body.unwrap())
7171
.send()
7272
.await
7373
} else {
74-
client.request(method, &url).send().await
74+
client.request(method, &request.url).send().await
7575
};
7676

7777
let stats = ResponseStats::from_response(response, start, dns_times).await;
@@ -85,17 +85,15 @@ async fn run_request(
8585
tx: mpsc::Sender<ResponseStats>,
8686
sent_tx: mpsc::Sender<Sent>,
8787
) -> Result<(Vec<ResponseStats>, std::time::Duration), PepeError> {
88-
let client = Arc::new(args.build_client()?);
88+
let request = args.request();
89+
let client = Arc::new(request.build_client()?);
8990
let all_start = std::time::Instant::now();
9091
let semaphore = Arc::new(Semaphore::new(args.concurrency as usize));
9192

9293
let handler = tokio::spawn({
9394
let client = client.clone();
9495
let tx = tx;
9596
let sent_tx = sent_tx;
96-
let url = args.url.clone();
97-
let method = args.method.clone();
98-
let body = args.body.clone();
9997
let number = args.number;
10098

10199
async move {
@@ -108,9 +106,7 @@ async fn run_request(
108106

109107
tokio::spawn(handle_request(
110108
client.clone(),
111-
url.clone(),
112-
method.clone(),
113-
body.clone(),
109+
request.clone(),
114110
tx.clone(),
115111
sent_tx.clone(),
116112
permit,

src/request.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use std::collections::HashMap;
2+
3+
use reqwest::{header::USER_AGENT, Proxy};
4+
5+
use crate::PepeError;
6+
7+
#[derive(Debug, Clone)]
8+
pub struct RequestSettings {
9+
pub timeout: u32,
10+
pub disable_compression: bool,
11+
pub disable_keepalive: bool,
12+
pub disable_redirects: bool,
13+
pub proxy: Option<String>,
14+
pub user_agent: String,
15+
}
16+
17+
#[derive(Debug, Clone)]
18+
pub struct Request {
19+
pub url: String,
20+
pub method: String,
21+
pub body: Option<String>,
22+
pub headers: HashMap<String, String>,
23+
pub settings: RequestSettings,
24+
}
25+
26+
impl Request {
27+
pub fn new(
28+
url: String,
29+
method: String,
30+
body: Option<String>,
31+
headers: Vec<String>,
32+
settings: RequestSettings,
33+
) -> Self {
34+
let mut header_map = HashMap::new();
35+
for header in headers {
36+
let parts: Vec<&str> = header.splitn(2, ':').collect();
37+
if parts.len() == 2 {
38+
header_map.insert(parts[0].trim().to_string(), parts[1].trim().to_string());
39+
}
40+
}
41+
42+
Self {
43+
url,
44+
method,
45+
body,
46+
headers: header_map,
47+
settings,
48+
}
49+
}
50+
51+
pub fn method(&self) -> reqwest::Method {
52+
reqwest::Method::from_bytes(self.method.as_bytes()).unwrap_or(reqwest::Method::GET)
53+
}
54+
55+
fn parse_headers(headers: &HashMap<String, String>) -> reqwest::header::HeaderMap {
56+
let mut request_headers = reqwest::header::HeaderMap::new();
57+
for (name, value) in headers {
58+
request_headers.insert(
59+
reqwest::header::HeaderName::from_bytes(name.as_bytes()).unwrap(),
60+
reqwest::header::HeaderValue::from_str(value).unwrap(),
61+
);
62+
}
63+
request_headers
64+
}
65+
66+
pub fn build_client(&self) -> Result<reqwest::Client, PepeError> {
67+
let mut request_headers = Self::parse_headers(&self.headers);
68+
69+
// Add user agent
70+
request_headers.insert(
71+
USER_AGENT,
72+
self.settings
73+
.user_agent
74+
.parse::<reqwest::header::HeaderValue>()
75+
.map_err(|e| PepeError::HeaderParseError(e.to_string()))?,
76+
);
77+
78+
let mut client_builder = reqwest::Client::builder()
79+
.default_headers(request_headers)
80+
.timeout(std::time::Duration::from_secs(self.settings.timeout as u64));
81+
82+
if let Some(proxy_url) = &self.settings.proxy {
83+
let proxy =
84+
Proxy::all(proxy_url).map_err(|e| PepeError::HeaderParseError(e.to_string()))?;
85+
client_builder = client_builder.proxy(proxy);
86+
}
87+
88+
if self.settings.disable_compression {
89+
client_builder = client_builder.no_gzip();
90+
}
91+
92+
if self.settings.disable_keepalive {
93+
client_builder = client_builder.connection_verbose(true);
94+
}
95+
96+
if self.settings.disable_redirects {
97+
client_builder = client_builder.redirect(reqwest::redirect::Policy::none());
98+
}
99+
100+
client_builder =
101+
client_builder.timeout(std::time::Duration::from_secs(self.settings.timeout as u64));
102+
103+
let client: reqwest::Client = client_builder
104+
.build()
105+
.map_err(|e| PepeError::RequestError(e))?;
106+
107+
Ok(client)
108+
}
109+
}

0 commit comments

Comments
 (0)