Enable "system" proxies by default (#683)
If no proxies are configured for a client, the environment (system) will be inspected automatically to set up proxies. Configuring a `Proxy` on a client or calling `no_proxy` will disable the use of the automatic system proxy. Closes #403
This commit is contained in:
		| @@ -102,4 +102,4 @@ env: | |||||||
|     - RUST_BACKTRACE=1 |     - RUST_BACKTRACE=1 | ||||||
| script: | script: | ||||||
|   - cargo build $FEATURES |   - cargo build $FEATURES | ||||||
|   - cargo test -v $FEATURES -- --test-threads=1 |   - cargo test -v $FEATURES --features __internal_proxy_sys_no_cache -- --test-threads=1 | ||||||
|   | |||||||
| @@ -37,6 +37,12 @@ json = ["serde_json"] | |||||||
|  |  | ||||||
| unstable-stream = [] | unstable-stream = [] | ||||||
|  |  | ||||||
|  | # Internal (PRIVATE!) features used to aid testing. | ||||||
|  | # Don't rely on these whatsoever. They may disappear at anytime. | ||||||
|  |  | ||||||
|  | # When enabled, disable using the cached SYS_PROXIES. | ||||||
|  | __internal_proxy_sys_no_cache = [] | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| http = "0.1.15" | http = "0.1.15" | ||||||
| url = "2.1" | url = "2.1" | ||||||
| @@ -49,6 +55,7 @@ futures-core-preview = { version = "=0.3.0-alpha.19" } | |||||||
| futures-util-preview = { version = "=0.3.0-alpha.19" } | futures-util-preview = { version = "=0.3.0-alpha.19" } | ||||||
| http-body = "=0.2.0-alpha.3" | http-body = "=0.2.0-alpha.3" | ||||||
| hyper = { version = "=0.13.0-alpha.4", default-features = false, features = ["tcp"] } | hyper = { version = "=0.13.0-alpha.4", default-features = false, features = ["tcp"] } | ||||||
|  | lazy_static = "1.4" | ||||||
| log = "0.4" | log = "0.4" | ||||||
| mime = "0.3.7" | mime = "0.3.7" | ||||||
| mime_guess = "2.0" | mime_guess = "2.0" | ||||||
| @@ -150,4 +157,3 @@ required-features = ["cookies"] | |||||||
| name = "gzip" | name = "gzip" | ||||||
| path = "tests/gzip.rs" | path = "tests/gzip.rs" | ||||||
| required-features = ["gzip"] | required-features = ["gzip"] | ||||||
|  |  | ||||||
|   | |||||||
| @@ -28,7 +28,6 @@ use crate::connect::Connector; | |||||||
| #[cfg(feature = "cookies")] | #[cfg(feature = "cookies")] | ||||||
| use crate::cookie; | use crate::cookie; | ||||||
| use crate::into_url::{expect_uri, try_uri}; | use crate::into_url::{expect_uri, try_uri}; | ||||||
| use crate::proxy::get_proxies; |  | ||||||
| use crate::redirect::{self, remove_sensitive_headers, RedirectPolicy}; | use crate::redirect::{self, remove_sensitive_headers, RedirectPolicy}; | ||||||
| #[cfg(feature = "tls")] | #[cfg(feature = "tls")] | ||||||
| use crate::tls::TlsBackend; | use crate::tls::TlsBackend; | ||||||
| @@ -69,6 +68,7 @@ struct Config { | |||||||
|     #[cfg(feature = "tls")] |     #[cfg(feature = "tls")] | ||||||
|     identity: Option<Identity>, |     identity: Option<Identity>, | ||||||
|     proxies: Vec<Proxy>, |     proxies: Vec<Proxy>, | ||||||
|  |     auto_sys_proxy: bool, | ||||||
|     redirect_policy: RedirectPolicy, |     redirect_policy: RedirectPolicy, | ||||||
|     referer: bool, |     referer: bool, | ||||||
|     timeout: Option<Duration>, |     timeout: Option<Duration>, | ||||||
| @@ -104,6 +104,7 @@ impl ClientBuilder { | |||||||
|                 connect_timeout: None, |                 connect_timeout: None, | ||||||
|                 max_idle_per_host: std::usize::MAX, |                 max_idle_per_host: std::usize::MAX, | ||||||
|                 proxies: Vec::new(), |                 proxies: Vec::new(), | ||||||
|  |                 auto_sys_proxy: true, | ||||||
|                 redirect_policy: RedirectPolicy::default(), |                 redirect_policy: RedirectPolicy::default(), | ||||||
|                 referer: true, |                 referer: true, | ||||||
|                 timeout: None, |                 timeout: None, | ||||||
| @@ -131,7 +132,11 @@ impl ClientBuilder { | |||||||
|     /// cannot load the system configuration. |     /// cannot load the system configuration. | ||||||
|     pub fn build(self) -> crate::Result<Client> { |     pub fn build(self) -> crate::Result<Client> { | ||||||
|         let config = self.config; |         let config = self.config; | ||||||
|         let proxies = Arc::new(config.proxies); |         let mut proxies = config.proxies; | ||||||
|  |         if config.auto_sys_proxy { | ||||||
|  |             proxies.push(Proxy::system()); | ||||||
|  |         } | ||||||
|  |         let proxies = Arc::new(proxies); | ||||||
|  |  | ||||||
|         let mut connector = { |         let mut connector = { | ||||||
|             #[cfg(feature = "tls")] |             #[cfg(feature = "tls")] | ||||||
| @@ -365,27 +370,28 @@ impl ClientBuilder { | |||||||
|     // Proxy options |     // Proxy options | ||||||
|  |  | ||||||
|     /// Add a `Proxy` to the list of proxies the `Client` will use. |     /// Add a `Proxy` to the list of proxies the `Client` will use. | ||||||
|  |     /// | ||||||
|  |     /// # Note | ||||||
|  |     /// | ||||||
|  |     /// Adding a proxy will disable the automatic usage of the "system" proxy. | ||||||
|     pub fn proxy(mut self, proxy: Proxy) -> ClientBuilder { |     pub fn proxy(mut self, proxy: Proxy) -> ClientBuilder { | ||||||
|         self.config.proxies.push(proxy); |         self.config.proxies.push(proxy); | ||||||
|  |         self.config.auto_sys_proxy = false; | ||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Clear all `Proxies`, so `Client` will use no proxy anymore. |     /// Clear all `Proxies`, so `Client` will use no proxy anymore. | ||||||
|  |     /// | ||||||
|  |     /// This also disables the automatic usage of the "system" proxy. | ||||||
|     pub fn no_proxy(mut self) -> ClientBuilder { |     pub fn no_proxy(mut self) -> ClientBuilder { | ||||||
|         self.config.proxies.clear(); |         self.config.proxies.clear(); | ||||||
|  |         self.config.auto_sys_proxy = false; | ||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Add system proxy setting to the list of proxies |     #[doc(hidden)] | ||||||
|     pub fn use_sys_proxy(mut self) -> ClientBuilder { |     #[deprecated(note = "the system proxy is used automatically")] | ||||||
|         let proxies = get_proxies(); |     pub fn use_sys_proxy(self) -> ClientBuilder { | ||||||
|         self.config.proxies.push(Proxy::custom(move |url| { |  | ||||||
|             if proxies.contains_key(url.scheme()) { |  | ||||||
|                 Some((*proxies.get(url.scheme()).unwrap()).clone()) |  | ||||||
|             } else { |  | ||||||
|                 None |  | ||||||
|             } |  | ||||||
|         })); |  | ||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -776,7 +782,7 @@ impl Client { | |||||||
|  |  | ||||||
| impl fmt::Debug for Client { | impl fmt::Debug for Client { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         let mut builder = f.debug_struct("ClientBuilder"); |         let mut builder = f.debug_struct("Client"); | ||||||
|         self.inner.fmt_fields(&mut builder); |         self.inner.fmt_fields(&mut builder); | ||||||
|         builder.finish() |         builder.finish() | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -193,18 +193,25 @@ impl ClientBuilder { | |||||||
|     // Proxy options |     // Proxy options | ||||||
|  |  | ||||||
|     /// Add a `Proxy` to the list of proxies the `Client` will use. |     /// Add a `Proxy` to the list of proxies the `Client` will use. | ||||||
|  |     /// | ||||||
|  |     /// # Note | ||||||
|  |     /// | ||||||
|  |     /// Adding a proxy will disable the automatic usage of the "system" proxy. | ||||||
|     pub fn proxy(self, proxy: Proxy) -> ClientBuilder { |     pub fn proxy(self, proxy: Proxy) -> ClientBuilder { | ||||||
|         self.with_inner(move |inner| inner.proxy(proxy)) |         self.with_inner(move |inner| inner.proxy(proxy)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Disable proxy setting. |     /// Clear all `Proxies`, so `Client` will use no proxy anymore. | ||||||
|  |     /// | ||||||
|  |     /// This also disables the automatic usage of the "system" proxy. | ||||||
|     pub fn no_proxy(self) -> ClientBuilder { |     pub fn no_proxy(self) -> ClientBuilder { | ||||||
|         self.with_inner(move |inner| inner.no_proxy()) |         self.with_inner(move |inner| inner.no_proxy()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Enable system proxy setting. |     #[doc(hidden)] | ||||||
|  |     #[deprecated(note = "the system proxy is used automatically")] | ||||||
|     pub fn use_sys_proxy(self) -> ClientBuilder { |     pub fn use_sys_proxy(self) -> ClientBuilder { | ||||||
|         self.with_inner(move |inner| inner.use_sys_proxy()) |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Timeout options |     // Timeout options | ||||||
|   | |||||||
| @@ -263,6 +263,9 @@ if_hyper! { | |||||||
|     #[macro_use] |     #[macro_use] | ||||||
|     extern crate doc_comment; |     extern crate doc_comment; | ||||||
|  |  | ||||||
|  |     #[macro_use] | ||||||
|  |     extern crate lazy_static; | ||||||
|  |  | ||||||
|     #[cfg(test)] |     #[cfg(test)] | ||||||
|     doctest!("../README.md"); |     doctest!("../README.md"); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										58
									
								
								src/proxy.rs
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								src/proxy.rs
									
									
									
									
									
								
							| @@ -180,11 +180,13 @@ impl Proxy { | |||||||
|         })) |         })) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /* |     pub(crate) fn system() -> Proxy { | ||||||
|     pub fn unix<P: AsRef<Path>(path: P) -> Proxy { |         if cfg!(feature = "__internal_proxy_sys_no_cache") { | ||||||
|  |             Proxy::new(Intercept::System(Arc::new(get_sys_proxies()))) | ||||||
|  |         } else { | ||||||
|  |             Proxy::new(Intercept::System(SYS_PROXIES.clone())) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|     */ |  | ||||||
|  |  | ||||||
|     fn new(intercept: Intercept) -> Proxy { |     fn new(intercept: Intercept) -> Proxy { | ||||||
|         Proxy { intercept } |         Proxy { intercept } | ||||||
| @@ -248,6 +250,9 @@ impl Proxy { | |||||||
|                     None |                     None | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             Intercept::System(ref map) => { | ||||||
|  |                 map.get(uri.scheme()).cloned() | ||||||
|  |             } | ||||||
|             Intercept::Custom(ref custom) => custom.call(uri), |             Intercept::Custom(ref custom) => custom.call(uri), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -257,6 +262,7 @@ impl Proxy { | |||||||
|             Intercept::All(_) => true, |             Intercept::All(_) => true, | ||||||
|             Intercept::Http(_) => uri.scheme() == "http", |             Intercept::Http(_) => uri.scheme() == "http", | ||||||
|             Intercept::Https(_) => uri.scheme() == "https", |             Intercept::Https(_) => uri.scheme() == "https", | ||||||
|  |             Intercept::System(ref map) => map.contains_key(uri.scheme()), | ||||||
|             Intercept::Custom(ref custom) => custom.call(uri).is_some(), |             Intercept::Custom(ref custom) => custom.call(uri).is_some(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -369,11 +375,14 @@ impl ProxyScheme { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type SystemProxyMap = HashMap<String, ProxyScheme>; | ||||||
|  |  | ||||||
| #[derive(Clone, Debug)] | #[derive(Clone, Debug)] | ||||||
| enum Intercept { | enum Intercept { | ||||||
|     All(ProxyScheme), |     All(ProxyScheme), | ||||||
|     Http(ProxyScheme), |     Http(ProxyScheme), | ||||||
|     Https(ProxyScheme), |     Https(ProxyScheme), | ||||||
|  |     System(Arc<SystemProxyMap>), | ||||||
|     Custom(Custom), |     Custom(Custom), | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -383,6 +392,7 @@ impl Intercept { | |||||||
|             Intercept::All(ref mut s) |             Intercept::All(ref mut s) | ||||||
|             | Intercept::Http(ref mut s) |             | Intercept::Http(ref mut s) | ||||||
|             | Intercept::Https(ref mut s) => s.set_basic_auth(username, password), |             | Intercept::Https(ref mut s) => s.set_basic_auth(username, password), | ||||||
|  |             Intercept::System(_) => unimplemented!(), | ||||||
|             Intercept::Custom(ref mut custom) => { |             Intercept::Custom(ref mut custom) => { | ||||||
|                 let header = encode_basic_auth(username, password); |                 let header = encode_basic_auth(username, password); | ||||||
|                 custom.auth = Some(header); |                 custom.auth = Some(header); | ||||||
| @@ -484,6 +494,10 @@ impl Dst for Uri { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | lazy_static! { | ||||||
|  |     static ref SYS_PROXIES: Arc<SystemProxyMap> = Arc::new(get_sys_proxies()); | ||||||
|  | } | ||||||
|  |  | ||||||
| /// Get system proxies information. | /// Get system proxies information. | ||||||
| /// | /// | ||||||
| /// It can only support Linux, Unix like, and windows system.  Note that it will always | /// It can only support Linux, Unix like, and windows system.  Note that it will always | ||||||
| @@ -493,8 +507,8 @@ impl Dst for Uri { | |||||||
| /// Returns: | /// Returns: | ||||||
| ///     System proxies information as a hashmap like | ///     System proxies information as a hashmap like | ||||||
| ///     {"http": Url::parse("http://127.0.0.1:80"), "https": Url::parse("https://127.0.0.1:80")} | ///     {"http": Url::parse("http://127.0.0.1:80"), "https": Url::parse("https://127.0.0.1:80")} | ||||||
| pub fn get_proxies() -> HashMap<String, Url> { | fn get_sys_proxies() -> SystemProxyMap { | ||||||
|     let proxies: HashMap<String, Url> = get_from_environment(); |     let proxies = get_from_environment(); | ||||||
|  |  | ||||||
|     // TODO: move the following #[cfg] to `if expression` when attributes on `if` expressions allowed |     // TODO: move the following #[cfg] to `if expression` when attributes on `if` expressions allowed | ||||||
|     #[cfg(target_os = "windows")] |     #[cfg(target_os = "windows")] | ||||||
| @@ -507,14 +521,14 @@ pub fn get_proxies() -> HashMap<String, Url> { | |||||||
|     proxies |     proxies | ||||||
| } | } | ||||||
|  |  | ||||||
| fn insert_proxy(proxies: &mut HashMap<String, Url>, schema: String, addr: String) { | fn insert_proxy(proxies: &mut SystemProxyMap, schema: String, addr: String) { | ||||||
|     if let Ok(valid_addr) = Url::parse(&addr) { |     if let Ok(valid_addr) = addr.into_proxy_scheme() { | ||||||
|         proxies.insert(schema, valid_addr); |         proxies.insert(schema, valid_addr); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| fn get_from_environment() -> HashMap<String, Url> { | fn get_from_environment() -> SystemProxyMap { | ||||||
|     let mut proxies: HashMap<String, Url> = HashMap::new(); |     let mut proxies = HashMap::new(); | ||||||
|  |  | ||||||
|     const PROXY_KEY_ENDS: &str = "_proxy"; |     const PROXY_KEY_ENDS: &str = "_proxy"; | ||||||
|  |  | ||||||
| @@ -530,7 +544,7 @@ fn get_from_environment() -> HashMap<String, Url> { | |||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(target_os = "windows")] | #[cfg(target_os = "windows")] | ||||||
| fn get_from_registry_impl() -> Result<HashMap<String, Url>, Box<dyn Error>> { | fn get_from_registry_impl() -> Result<SystemProxyMap, Box<dyn Error>> { | ||||||
|     let hkcu = RegKey::predef(HKEY_CURRENT_USER); |     let hkcu = RegKey::predef(HKEY_CURRENT_USER); | ||||||
|     let internet_setting: RegKey = |     let internet_setting: RegKey = | ||||||
|         hkcu.open_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?; |         hkcu.open_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?; | ||||||
| @@ -542,7 +556,7 @@ fn get_from_registry_impl() -> Result<HashMap<String, Url>, Box<dyn Error>> { | |||||||
|         return Ok(HashMap::new()); |         return Ok(HashMap::new()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let mut proxies: HashMap<String, Url> = HashMap::new(); |     let mut proxies = HashMap::new(); | ||||||
|     if proxy_server.contains("=") { |     if proxy_server.contains("=") { | ||||||
|         // per-protocol settings. |         // per-protocol settings. | ||||||
|         for p in proxy_server.split(";") { |         for p in proxy_server.split(";") { | ||||||
| @@ -589,7 +603,7 @@ fn get_from_registry_impl() -> Result<HashMap<String, Url>, Box<dyn Error>> { | |||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(target_os = "windows")] | #[cfg(target_os = "windows")] | ||||||
| fn get_from_registry() -> HashMap<String, Url> { | fn get_from_registry() -> SystemProxyMap { | ||||||
|     get_from_registry_impl().unwrap_or(HashMap::new()) |     get_from_registry_impl().unwrap_or(HashMap::new()) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -685,24 +699,30 @@ mod tests { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_get_proxies() { |     fn test_get_sys_proxies() { | ||||||
|         // save system setting first. |         // save system setting first. | ||||||
|         let system_proxy = env::var("http_proxy"); |         let system_proxy = env::var("http_proxy"); | ||||||
|  |  | ||||||
|         // remove proxy. |         // remove proxy. | ||||||
|         env::remove_var("http_proxy"); |         env::remove_var("http_proxy"); | ||||||
|         assert_eq!(get_proxies().contains_key("http"), false); |         assert_eq!(get_sys_proxies().contains_key("http"), false); | ||||||
|  |  | ||||||
|         // the system proxy setting url is invalid. |         // the system proxy setting url is invalid. | ||||||
|         env::set_var("http_proxy", "123465"); |         env::set_var("http_proxy", "123465"); | ||||||
|         assert_eq!(get_proxies().contains_key("http"), false); |         assert_eq!(get_sys_proxies().contains_key("http"), false); | ||||||
|  |  | ||||||
|         // set valid proxy |         // set valid proxy | ||||||
|         env::set_var("http_proxy", "http://127.0.0.1/"); |         env::set_var("http_proxy", "http://127.0.0.1/"); | ||||||
|         let proxies = get_proxies(); |         let proxies = get_sys_proxies(); | ||||||
|         let http_target = proxies.get("http").unwrap().as_str(); |  | ||||||
|  |         match &proxies["http"] { | ||||||
|  |             ProxyScheme::Http { uri, .. } => { | ||||||
|  |                 assert_eq!(uri, "http://127.0.0.1/"); | ||||||
|  |             }, | ||||||
|  |             #[cfg(feature = "socks")] | ||||||
|  |             _ => panic!("socks not expected"), | ||||||
|  |         } | ||||||
|  |  | ||||||
|         assert_eq!(http_target, "http://127.0.0.1/"); |  | ||||||
|         // reset user setting. |         // reset user setting. | ||||||
|         match system_proxy { |         match system_proxy { | ||||||
|             Err(_) => env::remove_var("http_proxy"), |             Err(_) => env::remove_var("http_proxy"), | ||||||
|   | |||||||
| @@ -119,30 +119,28 @@ async fn test_no_proxy() { | |||||||
|     assert_eq!(res.status(), reqwest::StatusCode::OK); |     assert_eq!(res.status(), reqwest::StatusCode::OK); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[cfg_attr(not(feature = "__internal_proxy_sys_no_cache"), ignore)] | ||||||
| #[tokio::test] | #[tokio::test] | ||||||
| async fn test_using_system_proxy() { | async fn test_using_system_proxy() { | ||||||
|     let url = "http://hyper.rs/prox"; |     let url = "http://not.a.real.sub.hyper.rs/prox"; | ||||||
|     let server = server::http(move |req| { |     let server = server::http(move |req| { | ||||||
|         assert_eq!(req.method(), "GET"); |         assert_eq!(req.method(), "GET"); | ||||||
|         assert_eq!(req.uri(), url); |         assert_eq!(req.uri(), url); | ||||||
|         assert_eq!(req.headers()["host"], "hyper.rs"); |         assert_eq!(req.headers()["host"], "not.a.real.sub.hyper.rs"); | ||||||
|  |  | ||||||
|         async { http::Response::default() } |         async { http::Response::default() } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     // Note: we're relying on the `__internal_proxy_sys_no_cache` feature to | ||||||
|  |     // check the environment every time. | ||||||
|  |  | ||||||
|     // save system setting first. |     // save system setting first. | ||||||
|     let system_proxy = env::var("http_proxy"); |     let system_proxy = env::var("http_proxy"); | ||||||
|     // set-up http proxy. |     // set-up http proxy. | ||||||
|     env::set_var("http_proxy", format!("http://{}", server.addr())); |     env::set_var("http_proxy", format!("http://{}", server.addr())); | ||||||
|  |  | ||||||
|     let res = reqwest::Client::builder() |     // system proxy is used by default | ||||||
|         .use_sys_proxy() |     let res = reqwest::get(url).await.unwrap(); | ||||||
|         .build() |  | ||||||
|         .unwrap() |  | ||||||
|         .get(url) |  | ||||||
|         .send() |  | ||||||
|         .await |  | ||||||
|         .unwrap(); |  | ||||||
|  |  | ||||||
|     assert_eq!(res.url().as_str(), url); |     assert_eq!(res.url().as_str(), url); | ||||||
|     assert_eq!(res.status(), reqwest::StatusCode::OK); |     assert_eq!(res.status(), reqwest::StatusCode::OK); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user