use std::future::poll_fn;
use std::pin::Pin;
use std::sync::{Arc, RwLock};
use proxy_watch::{
EnvPrecedence, ProxyConfig, ProxyEnv, ProxyStep, ProxyWatcher, Stream, Url, WatchEvent, resolve,
};
fn pick_proxy(config: &ProxyConfig, url: &Url) -> Option<Url> {
match resolve(config, url) {
Ok(steps) => steps.first().and_then(ProxyStep::to_url),
Err(error) => {
eprintln!(
"cannot resolve a proxy for {}: {error}",
without_credentials(url)
);
None
}
}
}
fn without_credentials(url: &Url) -> String {
let mut safe = url.clone();
if safe.set_username("").is_err() || safe.set_password(None).is_err() {
return format!("<{} URL withheld: credentials not maskable>", url.scheme());
}
safe.into()
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let env = ProxyEnv::from_env()?;
let mut watcher = ProxyWatcher::new()?;
let merge = move |os: ProxyConfig| os.with_env(&env, EnvPrecedence::BeforeSystem);
let shared = Arc::new(RwLock::new(merge(watcher.current())));
let updates = Arc::clone(&shared);
std::thread::spawn(move || {
while let Some(item) =
futures_executor::block_on(poll_fn(|cx| Pin::new(&mut watcher).poll_next(cx)))
{
match item {
WatchEvent::Snapshot { state, .. } => {
*updates.write().expect("lock is never poisoned") = merge(state.config);
}
WatchEvent::Error { error, .. } => {
eprintln!("proxy watch error; routing on the last good snapshot: {error}");
}
_ => {}
}
}
});
let for_proxy = Arc::clone(&shared);
let proxy = reqwest::Proxy::custom(move |url| {
let Ok(config) = for_proxy.read() else {
eprintln!(
"the proxy snapshot lock is poisoned; {} goes direct",
without_credentials(url)
);
return None;
};
pick_proxy(&config, url)
});
let _client = reqwest::Client::builder().proxy(proxy).build()?;
let config = shared.read().expect("lock is never poisoned");
println!("effective: {:?}", config.effective);
println!("sources:");
for (source, mode) in &config.sources {
println!(" {source:?} -> {mode:?}");
}
println!();
for input in [
"http://example.com/",
"https://example.com/",
"http://localhost:8080/",
] {
let url = Url::parse(input)?;
match pick_proxy(&config, &url).as_ref().map(without_credentials) {
Some(proxy) => println!("{input} -> {proxy}"),
None => println!("{input} -> DIRECT"),
}
}
Ok(())
}