-
Notifications
You must be signed in to change notification settings - Fork 118
Add --bypass-proxy argument to ignore proxy for hosts or IP addresses. #410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
blyxxyz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
For the name, maybe --disable-proxy-for? Bit of a mouthful though.
Some of the ideas I had about the name:
Is there some "guideline"/design principle that's used to name flags? I originally added support for |
|
Most of the flags are inherited from HTTPie, so we don't have a lot of experience coming up with names unfortunately.
Uhh, I'll use an LLM. I'll pretend these options already exist and ask it what they do and read what it hallucinates. That hopefully gets close to the intuition of real users. First I ask
And it doesn't get the syntax quite right but it correctly guesses what the option is for. Then I ask
And it gives examples where it doesn't take an argument,
I also asked it
And it came up with this, which is pretty interesting: We have the But maybe it's a little too cute, and I'd first want to check if the syntax for the Finally I asked it
and it hallucinated Take all of this with a grain of salt of course. |
|
@blyxxyz Can you take a look at the latest changes? And maybe also see if there's anything you'd like to add to the issue I raised at seanmonstar/reqwest#2579 ? |
This is getting into software archeology, but it kinda looks like httpie's syntax for proxies is a direct mapping of the underlying requests library: https://docs.python-requests.org/en/latest/user/advanced/#proxies I do like the idea of adding something like |
blyxxyz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again! A few more notes.
| #[test] | ||
| fn disable_proxy_for_trims_whitespace() { | ||
| assert_eq!(DisableProxyFor::from("*"), DisableProxyFor::from(" * ")); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you run this via Cli::try_parse_from in case clap one day decides to stop using From<&str> or something?
| /// The environment variable "NO_PROXY"/"no_proxy" can also be used, but its completely ignored | ||
| /// if --disable-proxy-for is passed. | ||
| #[clap(long, value_name = "no-proxy-list", value_delimiter = ',')] | ||
| pub disable_proxy_for: Vec<DisableProxyFor>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After sleeping on it I like your --bypass-proxy naming best, because it's shorter, because --no-bypass-proxy seems clearer than --no-disable-proxy-for, and because the "bypass" terminology is also used by other CLIs like chromium and netsh on Windows.
(Sorry for the churn!)
| /// if --disable-proxy-for is passed. | ||
| #[clap(long, value_name = "no-proxy-list", value_delimiter = ',')] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One effect of doing it this way is that --bypass-proxy=example.com --bypass-proxy=example.org is equivalent to --bypass-proxy=example.com,example.org. This is different from curl, but it might be desirable.
If we want to commit to it then we should have a test for it so it doesn't regress and we should maybe mention it in the docs.
curl's manpage says:
If --noproxy is provided several times, the last set value will be used.
We could say:
--bypass-proxy can be provided several times. The list can be cleared with --no-bypass-proxy.
|
|
||
| Ok(proxy.no_proxy( | ||
| reqwest::NoProxy::from_string(&noproxy_comma_delimited) | ||
| .or_else(reqwest::NoProxy::from_env), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally we'd also apply the * handling to the environment variable.
I'm also OK with deferring that for now since it's annoying to handle here and we haven't heard back from reqwest yet. In that case could you put a code comment?
| Proxy::All(url) => reqwest::Proxy::all(url), | ||
| }?; | ||
|
|
||
| let mut noproxy_comma_delimited = disable_proxy_for.join(","); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This causes a weird effect where --disable-proxy-for='' doesn't override $NO_PROXY but --disable-proxy-for='' --disable-proxy-for='' does override it.
Perhaps:
- if
disable_proxy_for.is_empty(), always do.no_proxy(reqwest::NoProxy::from_env()). - Otherwise, do
from_string()without a fallback.
| let mut proxy_server = server::http(|_| async move { | ||
| hyper::Response::builder() | ||
| .status(200) | ||
| .body("Proxy shouldn't have been used.".into()) | ||
| .unwrap() | ||
| }); | ||
| let mut actual_server = server::http(|_| async move { | ||
| hyper::Response::builder() | ||
| .status(200) | ||
| .body("".into()) | ||
| .unwrap() | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| let mut proxy_server = server::http(|_| async move { | |
| hyper::Response::builder() | |
| .status(200) | |
| .body("Proxy shouldn't have been used.".into()) | |
| .unwrap() | |
| }); | |
| let mut actual_server = server::http(|_| async move { | |
| hyper::Response::builder() | |
| .status(200) | |
| .body("".into()) | |
| .unwrap() | |
| }); | |
| let mut proxy_server = server::http(|_| async move { | |
| hyper::Response::builder() | |
| .status(200) | |
| .body("Proxy response".into()) | |
| .unwrap() | |
| }); | |
| let mut actual_server = server::http(|_| async move { | |
| hyper::Response::builder() | |
| .status(200) | |
| .body("Non-proxy response".into()) | |
| .unwrap() | |
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you also test the environment variable?
| impl Proxy { | ||
| pub fn into_reqwest_proxy( | ||
| self, | ||
| disable_proxy_for: &[DisableProxyFor], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you instead (or additionally) have a function to convert &[DisableProxyFor] to NoProxy? Then we only have to process it once.
Bonus points if you do it in Cli::try_parse_from and store the NoProxy as a field in the Cli struct, since then the fully processed IpMatcher and DomainMatcher get printed by --debug.
I saw #296 as a good first issue and thought I'd have a go at it.
A few things to note:
--noproxy), as that seems to be what you were aiming for. This could potentially be confusing for users, as it's similar to--no-proxy, which undoes any--proxyarguments.NoProxy's applies a wildcard (*) only to hostnames, not IP addresses. curl does both. So there's a little workaround to implicitly add IPv4 and IPv6 full ranges (respectively0.0.0.0/0and::/0).lohas that by default), and for hostnames, I thought I could use--resolve, but that turns the hostname into an IP address, so the hostname never reaches reqwest. If needed, I can take a closer look to see if I can add these tests.