asknothingx2_util/api/
config.rs

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    // Timeouts
33    pub request_timeout: Duration,
34    pub connection_timeout: Duration,
35
36    // Performance
37    pub max_connections: usize,
38    pub keep_connections_for: Duration,
39    pub detect_dead_connections: Option<Duration>,
40
41    // Network settings
42    pub proxy_url: Option<String>,
43    pub custom_certificates: Vec<Certificate>,
44    pub default_headers: HeaderMap,
45    pub user_agent: String,
46
47    // Behavior
48    pub follow_redirects: u32,
49    pub save_cookies: bool,
50    pub send_referer: bool,
51    pub compress_requests: bool,
52
53    // Security
54    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    // Advanced (usually don't need to change)
60    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        // TCP keepalive
293        if let Some(keepalive) = self.detect_dead_connections {
294            builder = builder.tcp_keepalive(Some(keepalive));
295        }
296
297        // Redirects
298        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        // HTTP/2 settings
311        if self.force_http2_only {
312            builder = builder.http2_prior_knowledge();
313        }
314
315        // TLS settings
316        if let Some(tls_ver) = &self.minimum_tls_version {
317            builder = builder.min_tls_version(*tls_ver);
318        }
319
320        // TLS settings
321        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        // Proxy
333        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        // Custom certificates
343        for cert in &self.custom_certificates {
344            builder = builder.add_root_certificate(cert.clone());
345        }
346
347        // Default headers
348        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}