speedtest/http.rs
1// File: src\http.rs
2// Author: Hadi Cahyadi <cumulus13@gmail.com>
3// Date: 2026-02-08
4// Description:
5// License: MIT
6
7use crate::error::Result;
8use crate::utils::{build_user_agent, cache_buster};
9use reqwest::blocking::{Client, Response};
10use std::time::Duration;
11
12pub struct HttpClient {
13 client: Client,
14 secure: bool,
15}
16
17impl HttpClient {
18 pub fn new(timeout: u64, secure: bool, source_address: Option<String>) -> Result<Self> {
19 let builder = Client::builder()
20 .timeout(Duration::from_secs(timeout))
21 .user_agent(build_user_agent())
22 .gzip(true);
23
24 // If source address is provided, bind to it
25 if let Some(_addr) = source_address {
26 // Note: reqwest doesn't directly support source address binding
27 // This would require lower-level socket manipulation
28 eprintln!("Warning: Source address binding not fully supported in this implementation");
29 }
30
31 let client = builder.build()?;
32
33 Ok(Self { client, secure })
34 }
35
36 pub fn get(&self, url: &str) -> Result<Response> {
37 let final_url = self.build_url(url)?;
38 let response = self.client.get(&final_url).send()?;
39 Ok(response)
40 }
41
42 pub fn post(&self, url: &str, body: Vec<u8>) -> Result<Response> {
43 let final_url = self.build_url(url)?;
44 let response = self
45 .client
46 .post(&final_url)
47 .header("Content-Type", "application/x-www-form-urlencoded")
48 .header("Cache-Control", "no-cache")
49 .body(body)
50 .send()?;
51 Ok(response)
52 }
53
54 pub fn get_text(&self, url: &str) -> Result<String> {
55 let response = self.get(url)?;
56 Ok(response.text()?)
57 }
58
59 pub fn get_bytes(&self, url: &str) -> Result<Vec<u8>> {
60 let response = self.get(url)?;
61 Ok(response.bytes()?.to_vec())
62 }
63
64 // fn build_url(&self, url: &str) -> Result<String> {
65 // let scheme = if url.starts_with(':') {
66 // if self.secure {
67 // "https"
68 // } else {
69 // "http"
70 // }
71 // } else {
72 // return Ok(url.to_string());
73 // };
74
75 // let url_without_colon = url.trim_start_matches(':');
76 // let delimiter = if url_without_colon.contains('?') {
77 // "&"
78 // } else {
79 // "?"
80 // };
81
82 // Ok(format!(
83 // "{}{}{}x={}",
84 // scheme,
85 // url_without_colon,
86 // delimiter,
87 // cache_buster()
88 // ))
89 // }
90 // fn build_url(&self, url: &str) -> Result<String> {
91 // if url.starts_with(':') {
92 // let scheme = if self.secure { "https" } else { "http" };
93 // let url_without_colon = url.trim_start_matches(':');
94 // let delimiter = if url_without_colon.contains('?') {
95 // "&"
96 // } else {
97 // "?"
98 // };
99 // // FIX: Added "://" after scheme + trim leading slashes
100 // Ok(format!(
101 // "{}://{}{}x={}",
102 // scheme,
103 // url_without_colon.trim_start_matches('/'),
104 // delimiter,
105 // cache_buster()
106 // ))
107 // } else {
108 // Ok(url.to_string())
109 // }
110 // }
111
112 // fn build_url(&self, url: &str) -> Result<String> {
113 // if url.starts_with("://") {
114 // let scheme = if self.secure { "https" } else { "http" };
115 // let rest = &url[3..]; // Skip "://"
116 // let delimiter = if rest.contains('?') {
117 // "&"
118 // } else {
119 // "?"
120 // };
121 // Ok(format!(
122 // "{}://{}{}x={}",
123 // scheme,
124 // rest,
125 // delimiter,
126 // cache_buster()
127 // ))
128 // } else {
129 // Ok(url.to_string())
130 // }
131 // }
132
133 fn build_url(&self, url: &str) -> Result<String> {
134 if url.starts_with("://") {
135 let scheme = if self.secure { "https" } else { "http" };
136 let rest = &url[3..];
137 let delimiter = if rest.contains('?') { "&" } else { "?" };
138 Ok(format!("{}://{}{}x={}", scheme, rest, delimiter, cache_buster()))
139 } else {
140 Ok(url.to_string())
141 }
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_build_url() {
151 let client = HttpClient::new(10, false, None).unwrap();
152
153 // Test with colon prefix
154 let url = client.build_url("://example.com/test").unwrap();
155 assert!(url.starts_with("http://example.com/test?x="));
156
157 // Test with secure
158 let secure_client = HttpClient::new(10, true, None).unwrap();
159 let url = secure_client.build_url("://example.com/test").unwrap();
160 assert!(url.starts_with("https://example.com/test?x="));
161
162 // Test with existing query
163 let url = client.build_url("://example.com/test?foo=bar").unwrap();
164 assert!(url.contains("&x="));
165 }
166}