atlas_http/
client_builder.rs

1use base64::{engine::general_purpose::STANDARD, Engine as _};
2use rustls::{ClientConfig, RootCertStore};
3use std::path::Path;
4use std::sync::Arc;
5use super::{CookieJar, HttpClient, HttpHeaders, HttpSyncClient, ProxyType};
6use crate::{tls_noverify, user_agent};
7
8#[derive(Debug, Clone)]
9pub struct HttpClientConfig {
10    pub tls_config: Arc<rustls::ClientConfig>,
11    pub user_agent: Option<String>,
12    pub headers: HttpHeaders,
13    pub cookie: CookieJar,
14    pub follow_location: bool,
15    pub timeout: u64,
16    pub proxy_type: ProxyType,
17    pub proxy_host: String,
18    pub proxy_port: u16,
19    pub proxy_user: String,
20    pub proxy_password: String,
21}
22
23pub struct HttpClientBuilder {
24    config: HttpClientConfig,
25}
26
27impl Default for HttpClientBuilder {
28    fn default() -> HttpClientBuilder {
29        Self::new()
30    }
31}
32
33impl HttpClientBuilder {
34    pub fn new() -> Self {
35        Self {
36            config: HttpClientConfig::default()
37        }
38    }
39
40    /// Finish building, and return asynchronous HTTP client
41    pub fn build_async(&mut self) -> HttpClient {
42        HttpClient::new(&self.config)
43    }
44
45    /// Finish building, and return blocking synchronous HTTP client
46    pub fn build_sync(&mut self) -> HttpSyncClient {
47        HttpSyncClient::new(&self.config)
48    }
49
50    /// Will always follow Location headers it encounters
51    pub fn follow_location(mut self) -> Self {
52        self.config.follow_location = true;
53        self
54    }
55
56    // Set timeout limit in seconds
57    pub fn timeout(mut self, seconds: u64) -> Self {
58        self.config.timeout = seconds;
59        self
60    }
61
62    /// Cookie jar file, will be auto-maintained unless you change auto-update to false via CookieJar::set_auto_update(bool) method.
63    pub fn cookie_jar(mut self, jar_file: &str) -> Self {
64        if !Path::new(&jar_file).exists() {
65            self.config.cookie.set_jar_file(jar_file);
66            self.config.cookie.set_auto_update(true);
67        } else {
68            self.config.cookie = CookieJar::from_file(jar_file, true).unwrap();
69        }
70        self
71    }
72
73    /// Set cookies from contents / lines of a Netscape formatted cookies.txt file
74    pub fn cookie_string(mut self, cookie_str: &str) -> Self {
75        self.config.cookie = CookieJar::from_string(&cookie_str.to_string());
76        self
77    }
78
79    /// Do not verify SSL certificates
80    pub fn noverify_ssl(mut self) -> Self {
81        // Initialize root store
82        let mut root_store = RootCertStore::empty();
83        root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
84
85        // Create config
86        let mut tls_config = ClientConfig::builder()
87            .with_root_certificates(root_store)
88            .with_no_client_auth();
89
90        tls_config.dangerous().set_certificate_verifier(Arc::new(
91            tls_noverify::NoCertificateVerification::new(rustls::crypto::ring::default_provider()),
92        ));
93
94        self.config.tls_config = Arc::new(tls_config);
95        self
96    }
97
98    /// Define user agent for session
99    pub fn user_agent(mut self, user_agent: &str) -> Self {
100        self.config.user_agent = Some(user_agent.to_string());
101        self
102    }
103
104    /// Set base headers to more closely emulate a web browser.
105    pub fn browser(mut self) -> Self {
106        // Create headers
107        self.config.headers = HttpHeaders::new();
108        self.config.headers.set(
109            "Accept",
110            "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
111        );
112        self.config.headers.set("Accept-Language", "en-US,en;q=0.5");
113        self.config.headers.set("Accept-Encoding", "identity");
114        self.config.headers.set("Connection", "close");
115
116        // User agent
117        if self.config.user_agent.is_none() {
118            self.config.user_agent = Some(user_agent::random());
119        }
120        self.config.follow_location = true;
121        self
122    }
123
124    // Define basic HTTP authentication
125    pub fn basic_auth(mut self, user: &str, password: &str) -> Self {
126        // Disable authentication, fi needed
127        if user.is_empty() {
128            self.config.headers.delete("Authorization");
129            return self;
130        }
131
132        // Enable authentication
133        let auth_userpass = format!("{}:{}", user, password);
134        let auth_line = format!("Basic {}", STANDARD.encode(auth_userpass));
135        self.config.headers.set("Authorization", auth_line.as_str());
136
137        self
138    }
139
140    /// Send requests over the Tor network.
141    pub fn tor(mut self) -> Self {
142        self.config.proxy_host = "127.0.0.1".to_string();
143        self.config.proxy_port = 9050;
144        self.config.proxy_type = ProxyType::SOCKS5;
145        self
146    }
147
148    // Send requests through a HTTP / SOCKS5 proxy
149    pub fn proxy(mut self, host: &str, port: &u16) -> Self {
150        if self.config.proxy_type == ProxyType::None {
151            self.config.proxy_type = ProxyType::SOCKS5;
152        }
153        self.config.proxy_host = host.to_string();
154        self.config.proxy_port = *port;
155        self
156    }
157
158    // Define authentication for the HTTP / SOCKS5 proxy
159    pub fn proxy_auth(mut self, user: &str, password: &str) -> Self {
160        self.config.proxy_user = user.to_string();
161        self.config.proxy_password = password.to_string();
162
163        if self.config.proxy_user.is_empty() {
164            self.config.headers.delete("Proxy-Authorization");
165        } else {
166            let auth_userpass = format!("{}:{}", user, password);
167            let auth_line = format!("Basic {}", STANDARD.encode(auth_userpass));
168            self.config
169                .headers
170                .set("Proxy-Authorization", auth_line.as_str());
171        }
172
173        self
174    }
175
176    /// Define whether it's a HTTP or SOCKS5 proxy
177    pub fn proxy_type(mut self, proxy_type: ProxyType) -> Self {
178        self.config.proxy_type = proxy_type;
179        self
180    }
181}
182
183impl Default for HttpClientConfig {
184    fn default() -> HttpClientConfig {
185
186        // Initialize root store
187        let mut root_store = RootCertStore::empty();
188        root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
189
190        // Create config
191        let tls_config = ClientConfig::builder()
192            .with_root_certificates(root_store)
193            .with_no_client_auth();
194
195        HttpClientConfig {
196            tls_config: Arc::new(tls_config),
197            user_agent: None,
198            headers: HttpHeaders::from_vec(&vec!["Connection: close".to_string()]),
199            cookie: CookieJar::new(),
200            follow_location: false,
201            timeout: 5,
202            proxy_type: ProxyType::None,
203            proxy_host: String::new(),
204            proxy_port: 0,
205            proxy_user: String::new(),
206            proxy_password: String::new(),
207        }
208
209    }
210}
211
212
213