clawser_browser/
client.rs1use crate::profile::generate_config_json;
19use crate::Result;
20use wreq_util::{Emulation, EmulationOS, EmulationOption};
21
22pub struct HttpClient {
27 inner: wreq::Client,
28 ua: String,
29}
30
31impl HttpClient {
32 pub fn new() -> Result<Self> {
34 Self::builder().build()
35 }
36
37 pub fn builder() -> HttpClientBuilder {
39 HttpClientBuilder::default()
40 }
41
42 pub fn get(&self, url: &str) -> wreq::RequestBuilder {
46 self.inner.get(url)
47 }
48
49 pub fn post(&self, url: &str) -> wreq::RequestBuilder {
51 self.inner.post(url)
52 }
53
54 pub fn put(&self, url: &str) -> wreq::RequestBuilder {
56 self.inner.put(url)
57 }
58
59 pub fn delete(&self, url: &str) -> wreq::RequestBuilder {
61 self.inner.delete(url)
62 }
63
64 pub fn patch(&self, url: &str) -> wreq::RequestBuilder {
66 self.inner.patch(url)
67 }
68
69 pub fn head(&self, url: &str) -> wreq::RequestBuilder {
71 self.inner.head(url)
72 }
73
74 pub fn request(&self, method: wreq::Method, url: &str) -> wreq::RequestBuilder {
76 self.inner.request(method, url)
77 }
78
79 pub fn user_agent(&self) -> &str {
83 &self.ua
84 }
85
86 pub fn wreq(&self) -> &wreq::Client {
88 &self.inner
89 }
90}
91
92pub struct HttpClientBuilder {
96 profile: Option<(usize, u64)>,
97 proxy: Option<String>,
98 timeout_secs: u64,
99 cookie_store: bool,
100}
101
102impl Default for HttpClientBuilder {
103 fn default() -> Self {
104 Self {
105 profile: None,
106 proxy: None,
107 timeout_secs: 30,
108 cookie_store: true,
109 }
110 }
111}
112
113impl HttpClientBuilder {
114 pub fn profile(mut self, index: usize, seed: u64) -> Self {
117 self.profile = Some((index, seed));
118 self
119 }
120
121 pub fn proxy(mut self, proxy: impl Into<String>) -> Self {
124 self.proxy = Some(proxy.into());
125 self
126 }
127
128 pub fn timeout(mut self, secs: u64) -> Self {
130 self.timeout_secs = secs;
131 self
132 }
133
134 pub fn cookie_store(mut self, enable: bool) -> Self {
136 self.cookie_store = enable;
137 self
138 }
139
140 pub fn build(self) -> Result<HttpClient> {
142 let (ua, languages) = if let Some((index, seed)) = self.profile {
144 let json = generate_config_json(index, seed);
145 let config: serde_json::Value = serde_json::from_str(&json)?;
146 let ua = config["navigator"]["user_agent"]
147 .as_str()
148 .unwrap_or(DEFAULT_UA)
149 .to_string();
150 let langs: Vec<String> = config["navigator"]["languages"]
151 .as_array()
152 .map(|arr| {
153 arr.iter()
154 .filter_map(|v| v.as_str().map(String::from))
155 .collect()
156 })
157 .unwrap_or_else(|| vec!["en-US".into(), "en".into()]);
158 (ua, langs.join(", "))
159 } else {
160 (DEFAULT_UA.to_string(), "en-US, en".to_string())
161 };
162
163 let emulation = EmulationOption::builder()
165 .emulation(Emulation::Chrome134)
166 .emulation_os(EmulationOS::Windows)
167 .skip_headers(true) .build();
169
170 let mut cb = wreq::Client::builder()
171 .emulation(emulation)
172 .cookie_store(self.cookie_store)
173 .user_agent(&ua)
174 .default_headers(build_chrome_headers(&ua, &languages))
175 .timeout(std::time::Duration::from_secs(self.timeout_secs));
176
177 if let Some(ref proxy_url) = self.proxy {
178 cb = cb.proxy(wreq::Proxy::all(proxy_url)?);
179 }
180
181 let client = cb.build()?;
182
183 Ok(HttpClient { inner: client, ua })
184 }
185}
186
187const DEFAULT_UA: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.0 Safari/537.36";
190
191const SEC_CH_UA: &str = r#""Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134""#;
192
193fn build_chrome_headers(ua: &str, languages: &str) -> wreq::header::HeaderMap {
195 use wreq::header::{self, HeaderMap, HeaderValue};
196
197 let mut h = HeaderMap::new();
198
199 h.insert(
201 header::ACCEPT,
202 HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"),
203 );
204 h.insert(
205 header::ACCEPT_ENCODING,
206 HeaderValue::from_static("gzip, deflate, br, zstd"),
207 );
208 if let Ok(v) = HeaderValue::from_str(languages) {
209 h.insert(header::ACCEPT_LANGUAGE, v);
210 }
211 h.insert("sec-ch-ua", HeaderValue::from_static(SEC_CH_UA));
212 h.insert("sec-ch-ua-mobile", HeaderValue::from_static("?0"));
213 h.insert("sec-ch-ua-platform", HeaderValue::from_static("\"Windows\""));
214 h.insert("sec-fetch-dest", HeaderValue::from_static("document"));
215 h.insert("sec-fetch-mode", HeaderValue::from_static("navigate"));
216 h.insert("sec-fetch-site", HeaderValue::from_static("none"));
217 h.insert("sec-fetch-user", HeaderValue::from_static("?1"));
218 h.insert(
219 header::UPGRADE_INSECURE_REQUESTS,
220 HeaderValue::from_static("1"),
221 );
222 if let Ok(v) = HeaderValue::from_str(ua) {
223 h.insert(header::USER_AGENT, v);
224 }
225
226 h
227}