Add support for system/environment proxies (#547)

This commit is contained in:
WindSoilder
2019-07-02 07:27:58 +08:00
committed by Sean McArthur
parent 564a08f230
commit 577d06c363
9 changed files with 255 additions and 5 deletions

View File

@@ -15,5 +15,5 @@ install:
- cargo -vV - cargo -vV
build: false build: false
test_script: test_script:
- cargo test - cargo test -- --test-threads=1
skip_branch_with_pr: true skip_branch_with_pr: true

View File

@@ -58,4 +58,4 @@ env:
script: script:
- cargo build $FEATURES - cargo build $FEATURES
- cargo test -v $FEATURES - cargo test -v $FEATURES -- --test-threads=1

View File

@@ -72,3 +72,6 @@ rustls-tls = ["hyper-rustls", "tokio-rustls", "webpki-roots", "rustls", "tls"]
trust-dns = ["trust-dns-resolver"] trust-dns = ["trust-dns-resolver"]
hyper-011 = ["hyper-old-types"] hyper-011 = ["hyper-old-types"]
[target.'cfg(windows)'.dependencies]
winreg = "0.6"

View File

@@ -36,6 +36,7 @@ use into_url::{expect_uri, try_uri};
use cookie; use cookie;
use redirect::{self, RedirectPolicy, remove_sensitive_headers}; use redirect::{self, RedirectPolicy, remove_sensitive_headers};
use {IntoUrl, Method, Proxy, StatusCode, Url}; use {IntoUrl, Method, Proxy, StatusCode, Url};
use ::proxy::get_proxies;
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
use {Certificate, Identity}; use {Certificate, Identity};
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
@@ -327,6 +328,26 @@ impl ClientBuilder {
self self
} }
/// Clear all `Proxies`, so `Client` will use no proxy anymore.
pub fn no_proxy(mut self) -> ClientBuilder {
self.config.proxies.clear();
self
}
/// Add system proxy setting to the list of proxies
pub fn use_sys_proxy(mut self) -> ClientBuilder {
let proxies = get_proxies();
self.config.proxies.push(Proxy::custom(move |url| {
return if proxies.contains_key(url.scheme()) {
Some((*proxies.get(url.scheme()).unwrap()).clone())
} else {
None
}
}));
self
}
/// Set a `RedirectPolicy` for this client. /// Set a `RedirectPolicy` for this client.
/// ///
/// Default will follow redirects up to a maximum of 10. /// Default will follow redirects up to a maximum of 10.

View File

@@ -84,6 +84,16 @@ impl ClientBuilder {
}) })
} }
/// Disable proxy setting.
pub fn no_proxy(self) -> ClientBuilder {
self.with_inner(move |inner| inner.no_proxy())
}
/// Enable system proxy setting.
pub fn use_sys_proxy(self) -> ClientBuilder {
self.with_inner(move |inner| inner.use_sys_proxy())
}
/// Set that all sockets have `SO_NODELAY` set to `true`. /// Set that all sockets have `SO_NODELAY` set to `true`.
pub fn tcp_nodelay(self) -> ClientBuilder { pub fn tcp_nodelay(self) -> ClientBuilder {
self.with_inner(move |inner| inner.tcp_nodelay()) self.with_inner(move |inner| inner.tcp_nodelay())
@@ -406,7 +416,8 @@ impl Client {
/// Creates a `ClientBuilder` to configure a `Client`. /// Creates a `ClientBuilder` to configure a `Client`.
/// ///
/// This is the same as `ClientBuilder::new()`. /// This builder will use system proxy setting, you can use
/// `reqwest::Client::builder().no_proxy()` to disable it.
pub fn builder() -> ClientBuilder { pub fn builder() -> ClientBuilder {
ClientBuilder::new() ClientBuilder::new()
} }

View File

@@ -133,6 +133,8 @@
//! A `Client` can be configured to make use of HTTP proxies by adding //! A `Client` can be configured to make use of HTTP proxies by adding
//! [`Proxy`](Proxy)s to a `ClientBuilder`. //! [`Proxy`](Proxy)s to a `ClientBuilder`.
//! //!
//! ** NOTE** System proxies will be used in the next breaking change.
//!
//! ## TLS //! ## TLS
//! //!
//! By default, a `Client` will make use of system-native transport layer //! By default, a `Client` will make use of system-native transport layer
@@ -206,6 +208,8 @@ extern crate url;
extern crate uuid; extern crate uuid;
#[cfg(feature = "socks")] #[cfg(feature = "socks")]
extern crate socks; extern crate socks;
#[cfg(target_os = "windows")]
extern crate winreg;
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
extern crate hyper_rustls; extern crate hyper_rustls;

View File

@@ -7,6 +7,14 @@ use http::{header::HeaderValue, Uri};
use hyper::client::connect::Destination; use hyper::client::connect::Destination;
use url::percent_encoding::percent_decode; use url::percent_encoding::percent_decode;
use {IntoUrl, Url}; use {IntoUrl, Url};
use std::collections::HashMap;
use std::env;
#[cfg(target_os = "windows")]
use std::error::Error;
#[cfg(target_os = "windows")]
use winreg::enums::HKEY_CURRENT_USER;
#[cfg(target_os = "windows")]
use winreg::RegKey;
/// Configuration of a proxy that a `Client` should pass requests to. /// Configuration of a proxy that a `Client` should pass requests to.
/// ///
@@ -469,6 +477,101 @@ impl Dst for Uri {
} }
} }
/// Get system proxies information.
///
/// It can only support Linux, Unix like, and windows system. Note that it will always
/// return a HashMap, even if something runs into error when find registry information in
/// Windows system. Note that invalid proxy url in the system setting will be ignored.
///
/// Returns:
/// 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")}
pub fn get_proxies() -> HashMap<String, Url> {
let proxies: HashMap<String, Url> = get_from_environment();
// TODO: move the following #[cfg] to `if expression` when attributes on `if` expressions allowed
#[cfg(target_os = "windows")]
{
if proxies.is_empty() {
// don't care errors if can't get proxies from registry, just return an empty HashMap.
return get_from_registry();
}
}
proxies
}
fn insert_proxy(proxies: &mut HashMap<String, Url>, schema: String, addr: String)
{
if let Ok(valid_addr) = Url::parse(&addr) {
proxies.insert(schema, valid_addr);
}
}
fn get_from_environment() -> HashMap<String, Url> {
let mut proxies: HashMap<String, Url> = HashMap::new();
const PROXY_KEY_ENDS: &str = "_proxy";
for (key, value) in env::vars() {
let key: String = key.to_lowercase();
if key.ends_with(PROXY_KEY_ENDS) {
let end_indx = key.len() - PROXY_KEY_ENDS.len();
let schema = &key[..end_indx];
insert_proxy(&mut proxies, String::from(schema), String::from(value));
}
}
proxies
}
#[cfg(target_os = "windows")]
fn get_from_registry_impl() -> Result<HashMap<String, Url>, Box<dyn Error>> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
let internet_setting: RegKey =
hkcu.open_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?;
// ensure the proxy is enable, if the value doesn't exist, an error will returned.
let proxy_enable: u32 = internet_setting.get_value("ProxyEnable")?;
let proxy_server: String = internet_setting.get_value("ProxyServer")?;
if proxy_enable == 0 {
return Ok(HashMap::new());
}
let mut proxies: HashMap<String, Url> = HashMap::new();
if proxy_server.contains("=") {
// per-protocol settings.
for p in proxy_server.split(";") {
let protocol_parts: Vec<&str> = p.split("=").collect();
match protocol_parts.as_slice() {
[protocol, address] => {
insert_proxy(&mut proxies, String::from(*protocol), String::from(*address));
}
_ => {
// Contains invalid protocol setting, just break the loop
// And make proxies to be empty.
proxies.clear();
break;
}
}
}
} else {
// Use one setting for all protocols.
if proxy_server.starts_with("http:") {
insert_proxy(&mut proxies, String::from("http"), proxy_server);
} else {
insert_proxy(&mut proxies, String::from("http"), format!("http://{}", proxy_server));
insert_proxy(&mut proxies, String::from("https"), format!("https://{}", proxy_server));
insert_proxy(&mut proxies, String::from("ftp"), format!("https://{}", proxy_server));
}
}
Ok(proxies)
}
#[cfg(target_os = "windows")]
fn get_from_registry() -> HashMap<String, Url> {
get_from_registry_impl().unwrap_or(HashMap::new())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -562,4 +665,30 @@ mod tests {
assert_eq!(intercepted_uri(&p, https), target1); assert_eq!(intercepted_uri(&p, https), target1);
assert!(p.intercept(&url(other)).is_none()); assert!(p.intercept(&url(other)).is_none());
} }
#[test]
fn test_get_proxies() {
// save system setting first.
let system_proxy = env::var("http_proxy");
// remove proxy.
env::remove_var("http_proxy");
assert_eq!(get_proxies().contains_key("http"), false);
// the system proxy setting url is invalid.
env::set_var("http_proxy", "123465");
assert_eq!(get_proxies().contains_key("http"), false);
// set valid proxy
env::set_var("http_proxy", "http://127.0.0.1/");
let proxies = get_proxies();
let http_target = proxies.get("http").unwrap().as_str();
assert_eq!(http_target, "http://127.0.0.1/");
// reset user setting.
match system_proxy {
Err(_) => env::remove_var("http_proxy"),
Ok(proxy) => env::set_var("http_proxy", proxy)
}
}
} }

View File

@@ -3,6 +3,8 @@ extern crate reqwest;
#[macro_use] #[macro_use]
mod support; mod support;
use std::env;
#[test] #[test]
fn http_proxy() { fn http_proxy() {
let server = server! { let server = server! {
@@ -115,3 +117,83 @@ fn http_proxy_basic_auth_parsed() {
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"proxied"); assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"proxied");
} }
#[test]
fn test_no_proxy() {
let server = server! {
request: b"\
GET /4 HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: test\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let proxy = format!("http://{}", server.addr());
let url = format!("http://{}/4", server.addr());
// set up proxy and use no_proxy to clear up client builder proxies.
let res = reqwest::Client::builder()
.proxy(
reqwest::Proxy::http(&proxy).unwrap()
)
.no_proxy()
.build()
.unwrap()
.get(&url)
.send()
.unwrap();
assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
}
#[test]
fn test_using_system_proxy() {
let server = server! {
request: b"\
GET http://hyper.rs/prox HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
host: hyper.rs\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: proxied\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
// save system setting first.
let system_proxy = env::var("http_proxy");
// set-up http proxy.
env::set_var("http_proxy", format!("http://{}", server.addr()));
let url = "http://hyper.rs/prox";
let res = reqwest::Client::builder()
.use_sys_proxy()
.build()
.unwrap()
.get(url)
.send()
.unwrap();
assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"proxied");
// reset user setting.
match system_proxy {
Err(_) => env::remove_var("http_proxy"),
Ok(proxy) => env::set_var("http_proxy", proxy)
}
}