proxy-watch
Read the operating system's proxy configuration on Windows, macOS, Linux, Android and
iOS, and get a Stream item every time it changes. On a host set to PAC, a URL's route
comes from the OS's own PAC engine or a bundled one.
use StreamExt;
use ;
A one-shot read at process start goes stale: code that read once and cached the answer
never sees a PAC URL pushed by policy, or a proxy switched out from under a long-running
program. This crate delivers that change as a Stream item.
Which part you need
- The settings once, for a program that starts, reads and exits:
read(). - The settings as they change, for a long-running program:
ProxyWatcher, aStreamthat opens with a snapshot. - The route for one URL, bypass rules applied:
resolve(), and a PAC engine from the feature table below when the host uses PAC or WPAD. - An HTTP client that follows the OS:
reqwest'sProxy::customfed by a watcher. - Only
http_proxy/no_proxy:ProxyEnv::from_env(), on every platform.
Each has a section under Usage.
Install
GNOME support, on by default, opens GLib at run time: a build needs no C library or headers, and a machine without GLib reads the other stores.
Other languages
The same library, built from this repository; each package's README has its usage.
| Language | Package | |
|---|---|---|
| Node.js | proxy-watch on npm |
|
| Python | proxy-watch on PyPI |
|
| C | header and libraries on the GitHub Releases page (bindings-v* tags) |
Usage
examples/ has a runnable file per section (current, watch, resolve,
reqwest_client), plus env, resolve_os_and_env and pac
(cargo run --example <name>; pac needs --features pac-quickjs). The API reference, every feature's limits and each
platform's details are on docs.rs.
Read it once
read() reports the OS settings only; layer http_proxy / no_proxy on top with
.with_env(&ProxyEnv::from_env()?, EnvPrecedence::BeforeSystem), or AfterSystem. On a
Linux host with no desktop store (most servers, containers and CI hosts), read() answers
Error::Unsupported: use ProxyEnv alone there.
Watch it
ProxyWatcher runs on OS threads of its own, so it needs no async runtime. This example
drives it with futures-executor; add futures-util and futures-executor yourself, or
.await next() from the runtime you have.
use StreamExt;
use ;
The first item is the current configuration; each later one is a change, coalesced over
200 ms. With the tokio feature, watch_channel() publishes to a
tokio::sync::watch::Receiver instead.
Decide how to reach a URL
resolve() turns a snapshot into the ordered ProxySteps to try, bypass rules applied:
use ;
On a machine configured with a PAC script or WPAD it fails with
Error::PacNotSupported instead of answering direct. pac::PacResolver answers there,
with the OS's engine (pac-native) or a bundled one (pac-quickjs, or pac-subprocess
to keep the script out of your process); examples/pac.rs shows one.
With reqwest
reqwest's Proxy::custom asks on every new connection, so a configuration kept fresh by
a watcher makes one Client follow the OS:
examples/reqwest_client.rs.
Pitfalls
read()can block for seconds on macOS and inside a Linux sandbox. Keep it off a UI thread.- Do not treat a
resolve()error as "go direct": on a PAC or WPAD machine that routes traffic around the administrator's proxy. - A sandboxed watcher never fires without
WatchOptions::poll_interval.
The rest, with each platform's limits, is on docs.rs.
Feature flags
| Feature | Default | What it adds |
|---|---|---|
resolve |
on | resolve(): the proxy, or direct, for one URL |
linux-gnome |
on | GNOME settings; opens GLib at run time |
linux-kde |
on | KDE settings (kioslaverc) |
tokio |
off | watch_channel(), a tokio::sync::watch::Receiver |
tracing |
off | Logs naming which source changed; written to keep credentials out of them |
pac-native |
off | PAC through the OS's own resolver on Windows, macOS, iOS and Android |
pac-quickjs |
off | A bundled QuickJS engine for a PAC body you supply; needs a C compiler |
pac-subprocess |
off | The same engine in a worker process of its own; the worker is built with pac-quickjs too |
pac-windows-native |
off | The Windows part of pac-native (WinHTTP) |
pac-macos-native |
off | The macOS part of pac-native (CFNetwork) |
pac-ios-native |
off | The iOS part of pac-native (CFNetwork) |
pac-android-native |
off | The Android part of pac-native (ProxySelector) |
pac |
off | PAC parsing and policy with no engine; every pac-* feature turns it on |
Platform support
| Platform | Status |
|---|---|
| Windows | Tested in CI |
| macOS | Tested in CI without a GUI. GUI changes, network-location switches and MDM GlobalHTTPProxy are unverified on real hardware |
| Linux (GNOME, KDE) | Tested in CI; behind linux-gnome and linux-kde |
| Linux (Flatpak/Snap) | Read through the desktop portal (needs linux-gnome); watching needs WatchOptions::poll_interval. Unverified |
| Android | API 23+; watching needs API 26+, or else WatchOptions::poll_interval. android-activity and Tauri 2.12+ hand the crate the app's JavaVM and Context; any other host calls proxy_watch::android::init before the first read, which otherwise fails, or aborts under panic = "abort". Partly verified on an emulator and a phone (Android 17) |
| iOS | Watching needs WatchOptions::poll_interval: iOS gives an app no proxy change notification. Partly verified on a simulator |
| Environment variables | ProxyEnv::from_env() on every platform; a snapshot, never a Stream |
Everywhere else the constructors compile and return Error::Unsupported at runtime.
Where the table says unverified, expect this failure mode: no proxy reported while the host has one, and no error. If you see it, the form asks for what the host is set to and what the crate answered; a report needs both.
Minimum supported Rust version
1.88, with every feature. CI checks it on Linux x86-64
(cargo check --all-targets --all-features); the other targets' dependencies are not
checked against it.
Design
- Async-runtime agnostic:
futures_core::Streamon dedicated OS threads (tokiooptional). unsafeis confined to the code that calls the OS directly. Windows and macOS backends, the iOS backend and the Core Foundation conversion it shares with macOS, the Android backend's JNI entry, the WinHTTP and CFNetwork PAC engines, the limitspac-subprocessputs on its worker (a memory cap throughsetrlimiton Linux and a job object on Windows, and the Linux seccomp filter), and the table through whichlinux-gnomecalls GLib; the OS-independent core and the rest of the Linux backend contain none of their own.- Credentials stay masked in
Debug;ProxyAuthhas noDisplay. That is theuser:password@of a URL; its query prints as it is, a PAC URL's?key=included, because nothing about a query parameter says it is a secret and the PAC URL is what tells two configurations apart.
Contributing
CONTRIBUTING.md has the checks CI applies and how to run them, what
#[ignore] means here (it marks the tests that rewrite your machine's real proxy settings)
and what the prose gates in xtask/ require of a comment.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.