1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
//! Wire `proxy-watch` into `reqwest` through `Proxy::custom`, and keep it live.
//!
//! `reqwest` resolves its proxy configuration when the `Client` is built and never
//! looks again (seanmonstar/reqwest#2674), so a `Client` created before the machine's
//! proxy is changed keeps using the old settings for the rest of the process.
//! `Proxy::custom` takes a closure that `reqwest` calls again afterwards, which is the
//! hook needed: keep the latest OS `ProxyConfig` in an `Arc<RwLock<_>>`, refresh it from
//! the `ProxyWatcher` stream, and — at each routing decision — merge in a fresh
//! [`ProxyEnv`](proxy_watch::ProxyEnv) snapshot (env-first, curl-like). Env vars are not
//! on the watcher stream; see `examples/resolve_os_and_env.rs` for the assembly and the
//! `--os-first` alternative.
//!
//! **Called when a connection is opened, not per request.** The routing decision is made
//! in `ConnectorService::call` — `reqwest-0.12.28` `src/connect.rs:928-954`, whose own log
//! line is `"starting new connection"` — which reaches the closure through
//! `Matcher::intercept` (`src/proxy.rs:528-538`). The pool key hyper-util files the
//! resulting connection under is the *destination's* `(scheme, authority)` and nothing else
//! (`hyper-util-0.1.20` `src/client/legacy/client.rs:92`), so a proxy change does not
//! invalidate a connection that already exists. It takes effect immediately for every
//! destination the client has no live connection to, and for the rest once that connection
//! goes — `pool_idle_timeout` defaults to 90 s, and an HTTP/2 origin is one connection
//! serving everything. `pool_max_idle_per_host(0)` trades pooling for immediacy; rebuilding
//! the `Client` on each `WatchEvent` is the other answer.
//!
//! It is easy to read the closure as per-request because `reqwest` does also call it that
//! way — but only to fill in a header, never to route. `Proxy::custom` sets
//! `maybe_has_http_auth = true; // never know` (`src/proxy.rs:411`), so every request to a
//! plaintext `http://` destination runs it once more looking for `Proxy-Authorization`
//! (`src/async_impl/client.rs:2659-2681`). A closure with a side effect will see those
//! calls; a route will not follow from them.
//!
//! `reqwest` is only a dev-dependency of this crate — nothing here is part of the
//! library. Run with:
//!
//! ```text
//! cargo run --example reqwest_client
//! http_proxy=http://127.0.0.1:8080 cargo run --example reqwest_client
//! ```
//!
//! The example builds the client and prints the decision the closure makes for a few
//! URLs; it sends no request, so it needs no network and no async runtime.
use poll_fn;
use Pin;
use ;
use ;
/// Merge OS + env (env first when set), then pick a proxy URL for `reqwest`.
///
/// `None` means "no proxy", which is what `reqwest` expects for a direct connection —
/// but not only that. [`ProxyStep::to_url`] also answers `None` for an endpoint whose
/// host cannot be written into a URL, and this function passes that through unchanged,
/// so a `None` here can mean "there is a proxy and it could not be rendered". `reqwest`
/// reads it as direct either way. [`ProxyStep::endpoint`] is what distinguishes them.
///
/// The URL that comes back carries any `user:password@` the machine had, because that
/// is what `reqwest` has to send. Do not print it — see [`without_credentials`].
///
/// Two different things land in the `Err` arm, and this function treats them alike. A PAC
/// or WPAD configuration lands there not because this version is missing something, but
/// because [`resolve`] is defined never to evaluate a script (`Error::PacNotSupported`).
/// `Error::ProxyEntryUnusable` lands there because the only entry that would have covered
/// the URL was configured and could not be read — the case the crate added that error to
/// stop answering `Direct` for. Note what this function then does with both: it prints,
/// and returns `None`, so the request goes **direct anyway**. That is a fail-open, and it
/// is deliberate only in the sense that a fail-open like this must still leave a trace
/// rather than pass in silence — the `eprintln!` is that trace. It is not a good default
/// for a corporate network, where PAC is common and going direct means leaving the proxy
/// the administrator configured. `Proxy::custom` gives the closure no way to fail a
/// request — `None` is the only other answer it takes — so a real integration decides this
/// outside the closure: enable the `pac` feature with an engine and call
/// `resolve_with_pac()` (see the `pac` example), and let a `ProxyEntryUnusable` stop the
/// caller rather than route it around the proxy. This example keeps the direct fallback so
/// that it stays runnable with `resolve` alone.
/// Re-read the environment and rank it above the watched OS snapshot.
///
/// Re-read rather than cached because this runs at each routing decision, and a process
/// that sets its own `*_proxy` after start-up would otherwise keep routing on the value it
/// had at boot. The reading is the only part this function owns; the ranking, the
/// `fallbacks` and the capture time all belong to
/// [`ProxyConfig::with_env`](proxy_watch::ProxyConfig::with_env).
/// Drop any `user:password@` before printing a URL — a proxy's or a destination's.
///
/// [`ProxyStep::to_url`] says "do not log" in as many words, and it means the rendered
/// URL, not just the raw password: it percent-encodes, and `p%40ss` is an encoding of
/// `p@ss`, not a mask of it. The crate hides credentials in everything it renders itself
/// — `ProxyMode`'s `Debug`, every `Error`'s `Display` — but `to_url` exists precisely to
/// hand them to a client, so past that point the masking is the caller's.
///
/// A `String` and not a `Url`, so that the failing path cannot be ignored by accident:
/// `Url::set_username` answers `Err` for a URL that cannot carry a username (`url` 2.5.8
/// refuses an absent or empty host, and the `file` scheme outright), and on `Err` the
/// clone is still the original — credentials and all. Print the marker instead. A mask
/// whose failure mode is "silently returns the input" is worse than no mask, because the
/// call site reads as if it were covered.