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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//! 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 `ProxyConfig` in an `Arc<RwLock<_>>` and refresh it from
//! the `ProxyWatcher` stream. Env vars are not on the watcher stream, so
//! [`ProxyEnv`] is read once at start-up and merged into every
//! snapshot as it arrives (env-first, curl-like); 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 headers, never to route. `Proxy::custom` sets both
//! `maybe_has_http_auth = true; // never know` and `maybe_has_http_custom_headers = true`
//! (`src/proxy.rs:410-412`), so every request to a plaintext `http://` destination runs it
//! twice more — once looking for `Proxy-Authorization`, once for custom proxy headers
//! (`src/async_impl/client.rs:2605-2606`, which calls `proxy_auth` and then
//! `proxy_custom_headers`). 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 *would* make for a
//! few URLs, by calling `pick_proxy` directly; it sends no request, so it needs no network
//! and no async runtime.
//!
//! **Unverified:** `reqwest` never calls the closure here. Every path that reaches it needs
//! a connection or a request, and this example makes neither, so the wiring above is
//! type-checked and never run.
//! **Risk:** should `reqwest` stop consulting the closure per connection — resolve it once
//! at build time, say — this file would still compile and print the same lines, and the
//! live-update claim at the top would be false with nothing here to say so.
//! **Symptom:** a proxy changed after the `Client` was built is never used by a new
//! connection. To check, count calls in `pick_proxy` while sending one request to a local
//! port nothing listens on: the connect fails, but the closure runs first.
use poll_fn;
use Pin;
use ;
use ;
/// Pick a proxy URL for `reqwest` from the merged OS + env configuration.
///
/// `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.
/// 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.