Add support for system/environment proxies (#547)
This commit is contained in:
committed by
Sean McArthur
parent
564a08f230
commit
577d06c363
@@ -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
|
||||||
|
|||||||
@@ -58,4 +58,4 @@ env:
|
|||||||
|
|
||||||
script:
|
script:
|
||||||
- cargo build $FEATURES
|
- cargo build $FEATURES
|
||||||
- cargo test -v $FEATURES
|
- cargo test -v $FEATURES -- --test-threads=1
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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())
|
||||||
@@ -369,7 +379,7 @@ impl ClientBuilder {
|
|||||||
|
|
||||||
/// Enable a persistent cookie store for the client.
|
/// Enable a persistent cookie store for the client.
|
||||||
///
|
///
|
||||||
/// Cookies received in responses will be preserved and included in
|
/// Cookies received in responses will be preserved and included in
|
||||||
/// additional requests.
|
/// additional requests.
|
||||||
///
|
///
|
||||||
/// By default, no cookie store is used.
|
/// By default, no cookie store is used.
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
129
src/proxy.rs
129
src/proxy.rs
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,7 +432,7 @@ fn test_appended_headers_not_overwritten() {
|
|||||||
let client = reqwest::Client::builder()
|
let client = reqwest::Client::builder()
|
||||||
.default_headers(headers)
|
.default_headers(headers)
|
||||||
.build().unwrap();
|
.build().unwrap();
|
||||||
|
|
||||||
let server = server! {
|
let server = server! {
|
||||||
request: b"\
|
request: b"\
|
||||||
GET /4 HTTP/1.1\r\n\
|
GET /4 HTTP/1.1\r\n\
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user