1use std::time::Duration;
2
3use http::{
4 header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, CONNECTION},
5 HeaderMap, HeaderValue,
6};
7use reqwest::{tls, Certificate, Client, Proxy};
8
9use super::{content_type::Application, error::ConfigError};
10
11mod user_agents {
12 pub const CLI: &str = "asknothingx2-cli/0.0.28";
13 pub const WEB: &str = "asknothingx2/0.0.28";
14 pub const PRODUCTION: &str = "asknothingx2-production/0.0.28";
15 pub const DEVELOPMENT: &str = "asknothingx2-dev/0.0.28";
16 pub const GATEWAY: &str = "asknothingx2-gateway/0.0.28";
17 pub const SCRAPING: &str =
18 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0";
19}
20
21mod custom_headers {
22 pub const X_CLIENT: &str = "x-client";
23 pub const X_DEVELOPMENT: &str = "x-development";
24 pub const DNT: &str = "dnt";
25 pub const UPGRADE_INSECURE_REQUESTS: &str = "upgrade-insecure-requests";
26}
27
28#[derive(Debug, Clone)]
29pub struct Config {
30 pub app_type: AppType,
31
32 pub request_timeout: Duration,
34 pub connection_timeout: Duration,
35
36 pub max_connections: usize,
38 pub keep_connections_for: Duration,
39 pub detect_dead_connections: Option<Duration>,
40
41 pub proxy_url: Option<String>,
43 pub custom_certificates: Vec<Certificate>,
44 pub default_headers: HeaderMap,
45 pub user_agent: String,
46
47 pub follow_redirects: u32,
49 pub save_cookies: bool,
50 pub send_referer: bool,
51 pub compress_requests: bool,
52
53 pub allow_invalid_certificates: bool,
55 pub allow_wrong_hostnames: bool,
56 pub require_https: bool,
57 pub minimum_tls_version: Option<tls::Version>,
58
59 pub prefer_http2: bool,
61 pub force_http2_only: bool,
62 pub async_dns: bool,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66pub enum AppType {
67 Cli,
68 Web,
69 Production,
70 Development,
71 Gateway,
72 Scraping,
73}
74
75impl Config {
76 pub fn for_cli_tools() -> Self {
77 let mut headers = HeaderMap::new();
78 headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
79
80 Self {
81 app_type: AppType::Cli,
82 request_timeout: Duration::from_secs(60),
83 connection_timeout: Duration::from_secs(10),
84 max_connections: 1,
85 keep_connections_for: Duration::from_secs(5),
86 detect_dead_connections: None,
87 proxy_url: None,
88 custom_certificates: Vec::new(),
89 default_headers: headers,
90 user_agent: user_agents::CLI.to_string(),
91 follow_redirects: 5,
92 save_cookies: false,
93 send_referer: true,
94 compress_requests: true,
95 allow_invalid_certificates: false,
96 allow_wrong_hostnames: false,
97 require_https: false,
98 minimum_tls_version: None,
99 prefer_http2: false,
100 force_http2_only: false,
101 async_dns: false,
102 }
103 }
104
105 pub fn for_web_apps() -> Self {
106 let mut headers = HeaderMap::new();
107 headers.insert(
108 ACCEPT,
109 HeaderValue::from_static("application/json, text/plain, */*"),
110 );
111 headers.insert(
112 ACCEPT_ENCODING,
113 HeaderValue::from_static("gzip, br, deflate"),
114 );
115
116 Self {
117 app_type: AppType::Web,
118 request_timeout: Duration::from_secs(30),
119 connection_timeout: Duration::from_secs(5),
120 max_connections: 10,
121 keep_connections_for: Duration::from_secs(90),
122 detect_dead_connections: Some(Duration::from_secs(60)),
123 proxy_url: None,
124 custom_certificates: Vec::new(),
125 default_headers: headers,
126 user_agent: user_agents::WEB.to_string(),
127 follow_redirects: 10,
128 save_cookies: false,
129 send_referer: true,
130 compress_requests: true,
131 allow_invalid_certificates: false,
132 allow_wrong_hostnames: false,
133 require_https: false,
134 minimum_tls_version: Some(tls::Version::TLS_1_2),
135 prefer_http2: true,
136 force_http2_only: false,
137 async_dns: true,
138 }
139 }
140
141 pub fn for_production() -> Self {
142 let mut headers = HeaderMap::new();
143 headers.insert(ACCEPT, Application::Json.to_header_value());
144 headers.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip, br"));
145 headers.insert(
146 custom_headers::X_CLIENT,
147 HeaderValue::from_static("asknothingx2-production"),
148 );
149
150 Self {
151 app_type: AppType::Production,
152 request_timeout: Duration::from_secs(10),
153 connection_timeout: Duration::from_secs(3),
154 max_connections: 50,
155 keep_connections_for: Duration::from_secs(300),
156 detect_dead_connections: Some(Duration::from_secs(30)),
157 proxy_url: None,
158 custom_certificates: Vec::new(),
159 default_headers: headers,
160 user_agent: user_agents::PRODUCTION.to_string(),
161 follow_redirects: 3,
162 save_cookies: false,
163 send_referer: false,
164 compress_requests: true,
165 allow_invalid_certificates: false,
166 allow_wrong_hostnames: false,
167 require_https: true,
168 minimum_tls_version: Some(tls::Version::TLS_1_3),
169 prefer_http2: true,
170 force_http2_only: true,
171 async_dns: true,
172 }
173 }
174
175 pub fn for_development() -> Self {
176 let mut headers = HeaderMap::new();
177 headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
178 headers.insert(
179 custom_headers::X_DEVELOPMENT,
180 HeaderValue::from_static("true"),
181 );
182
183 Self {
184 app_type: AppType::Development,
185 request_timeout: Duration::from_secs(5),
186 connection_timeout: Duration::from_secs(2),
187 max_connections: 1,
188 keep_connections_for: Duration::from_secs(1),
189 detect_dead_connections: None,
190 proxy_url: None,
191 custom_certificates: Vec::new(),
192 default_headers: headers,
193 user_agent: user_agents::DEVELOPMENT.to_string(),
194 follow_redirects: 0,
195 save_cookies: false,
196 send_referer: false,
197 compress_requests: true,
198 allow_invalid_certificates: true,
199 allow_wrong_hostnames: true,
200 require_https: false,
201 minimum_tls_version: None,
202 prefer_http2: false,
203 force_http2_only: false,
204 async_dns: false,
205 }
206 }
207
208 pub fn for_api_gateway() -> Self {
209 let mut headers = HeaderMap::new();
210 headers.insert(ACCEPT, Application::Json.to_header_value());
211
212 Self {
213 app_type: AppType::Gateway,
214 request_timeout: Duration::from_secs(5),
215 connection_timeout: Duration::from_secs(1),
216 max_connections: 100,
217 keep_connections_for: Duration::from_secs(600),
218 detect_dead_connections: Some(Duration::from_secs(15)),
219 proxy_url: None,
220 custom_certificates: Vec::new(),
221 default_headers: headers,
222 user_agent: user_agents::GATEWAY.to_string(),
223 follow_redirects: 0,
224 save_cookies: false,
225 send_referer: false,
226 compress_requests: true,
227 allow_invalid_certificates: false,
228 allow_wrong_hostnames: false,
229 require_https: false,
230 minimum_tls_version: Some(tls::Version::TLS_1_3),
231 prefer_http2: true,
232 force_http2_only: true,
233 async_dns: true,
234 }
235 }
236
237 pub fn for_web_scraping() -> Self {
238 let mut headers = HeaderMap::new();
239 headers.insert(
240 ACCEPT,
241 HeaderValue::from_static(
242 "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
243 ),
244 );
245 headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.5"));
246 headers.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip,deflate,br"));
247 headers.insert(custom_headers::DNT, HeaderValue::from_static("1"));
248 headers.insert(CONNECTION, HeaderValue::from_static("keep-alive"));
249 headers.insert(
250 custom_headers::UPGRADE_INSECURE_REQUESTS,
251 HeaderValue::from_static("1"),
252 );
253
254 Self {
255 app_type: AppType::Scraping,
256 request_timeout: Duration::from_secs(30),
257 connection_timeout: Duration::from_secs(10),
258 max_connections: 5,
259 keep_connections_for: Duration::from_secs(60),
260 detect_dead_connections: Some(Duration::from_secs(60)),
261 proxy_url: None,
262 custom_certificates: Vec::new(),
263 default_headers: headers,
264 user_agent: user_agents::SCRAPING.to_string(),
265 follow_redirects: 10,
266 save_cookies: true,
267 send_referer: true,
268 compress_requests: true,
269 allow_invalid_certificates: false,
270 allow_wrong_hostnames: false,
271 require_https: false,
272 minimum_tls_version: Some(tls::Version::TLS_1_2),
273 prefer_http2: true,
274 force_http2_only: false,
275 async_dns: true,
276 }
277 }
278
279 pub fn build_client(&self) -> Result<Client, ConfigError> {
280 let mut builder = Client::builder()
281 .timeout(self.request_timeout)
282 .connect_timeout(self.connection_timeout)
283 .pool_max_idle_per_host(self.max_connections)
284 .pool_idle_timeout(self.keep_connections_for)
285 .user_agent(&self.user_agent)
286 .gzip(self.compress_requests)
287 .brotli(self.compress_requests)
288 .deflate(self.compress_requests)
289 .referer(self.send_referer)
290 .cookie_store(self.save_cookies);
291
292 if let Some(keepalive) = self.detect_dead_connections {
294 builder = builder.tcp_keepalive(Some(keepalive));
295 }
296
297 builder = if self.follow_redirects == 0 {
299 builder.redirect(reqwest::redirect::Policy::none())
300 } else {
301 builder.redirect(reqwest::redirect::Policy::limited(
302 self.follow_redirects as usize,
303 ))
304 };
305
306 if self.prefer_http2 && !self.force_http2_only {
307 builder = builder.http2_adaptive_window(true);
308 }
309
310 if self.force_http2_only {
312 builder = builder.http2_prior_knowledge();
313 }
314
315 if let Some(tls_ver) = &self.minimum_tls_version {
317 builder = builder.min_tls_version(*tls_ver);
318 }
319
320 if self.allow_invalid_certificates {
322 builder = builder.danger_accept_invalid_certs(true);
323 }
324 if self.allow_wrong_hostnames {
325 builder = builder.danger_accept_invalid_hostnames(true);
326 }
327
328 if self.async_dns {
329 builder = builder.hickory_dns(true);
330 }
331
332 if let Some(proxy_url) = &self.proxy_url {
334 let proxy = Proxy::all(proxy_url).map_err(|e| ConfigError::InvalidProxyUrl {
335 url: proxy_url.to_string(),
336 reason: e.to_string(),
337 source: e,
338 })?;
339 builder = builder.proxy(proxy);
340 }
341
342 for cert in &self.custom_certificates {
344 builder = builder.add_root_certificate(cert.clone());
345 }
346
347 if !self.default_headers.is_empty() {
349 builder = builder.default_headers(self.default_headers.clone());
350 }
351
352 builder.build().map_err(|e| ConfigError::ClientBuildFailed {
353 reason: e.to_string(),
354 source: e,
355 })
356 }
357}