Skip to main content

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}