Skip to content
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

Add support for the no_proxy env var mechanism in the HTTP client #4445

Merged
merged 23 commits into from
Mar 22, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
01303f4
Added no proxy check for issue #4431
scirelli-c1 Dec 15, 2019
40a109e
Updated CONTRIBUTORS file.
scirelli-c1 Dec 15, 2019
a9d23cd
Added CHANGES file
scirelli-c1 Dec 15, 2019
1ba92e3
Merge branch 'master' into no_proxy2
scirelli Feb 4, 2021
5fc8f4c
Fixed linting errors
scirelli Feb 4, 2021
8061077
Moved proxy_bypass into client.py
scirelli Feb 4, 2021
7566d86
Drop unrelated gitignore change
webknjaz Mar 6, 2021
532d979
Rephrase the change note to be more specific
webknjaz Mar 6, 2021
9d90569
Merge branch 'master' of github.com:aio-libs/aiohttp into no_proxy2
scirelli Mar 7, 2021
d3aa076
Merge branch 'no_proxy2' of github.com:scirelli/aiohttp into no_proxy2
scirelli Mar 7, 2021
8199ab4
Factored out proxy check, and wrote unit tests
scirelli Mar 8, 2021
4b2a032
Refactor to remove for loop.
scirelli Mar 8, 2021
445428c
Update aiohttp/client.py
scirelli Mar 9, 2021
74a17c2
Update aiohttp/helpers.py
scirelli Mar 9, 2021
37393c6
Updated based on PR comments.
scirelli Mar 9, 2021
5df1e7b
Turn the parametrize args into separate tuple items
webknjaz Mar 18, 2021
d1966e4
Add explicit pytest ids for the positive case
webknjaz Mar 18, 2021
a73ddac
Parametrize the negative tests
webknjaz Mar 18, 2021
3353d1d
Adjust the formatting of the tests
webknjaz Mar 18, 2021
e717b2b
Updated based on PR comments.
scirelli Mar 20, 2021
ad462ec
Updated based on PR comments
scirelli Mar 22, 2021
9796cb5
Drop unnecessary fixtures
webknjaz Mar 22, 2021
9e4408a
Format the usefixtures invocation
webknjaz Mar 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/4431.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed HTTP client requests to honor ``no_proxy`` environment variables.
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ Stanislav Prokop
Stefan Tjarks
Stepan Pletnev
Stephan Jaensch
Stephen Cirelli
Stephen Granade
Steven Seguin
Sunghyun Hwang
Expand Down
10 changes: 4 additions & 6 deletions aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import sys
import traceback
import warnings
from contextlib import suppress
from types import SimpleNamespace, TracebackType
from typing import (
Any,
Expand Down Expand Up @@ -80,7 +81,7 @@
BasicAuth,
TimeoutHandle,
ceil_timeout,
proxies_from_env,
get_env_proxy_for_url,
sentinel,
strip_auth_from_url,
)
Expand Down Expand Up @@ -444,11 +445,8 @@ async def _request(
if proxy is not None:
proxy = URL(proxy)
elif self._trust_env:
for scheme, proxy_info in proxies_from_env().items():
if scheme == url.scheme:
proxy = proxy_info.proxy
proxy_auth = proxy_info.proxy_auth
break
with suppress(LookupError):
proxy, proxy_auth = get_env_proxy_for_url(url)

req = self._request_class(
method,
Expand Down
16 changes: 15 additions & 1 deletion aiohttp/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
cast,
)
from urllib.parse import quote
from urllib.request import getproxies
from urllib.request import getproxies, proxy_bypass

import async_timeout
from multidict import CIMultiDict, MultiDict, MultiDictProxy
Expand Down Expand Up @@ -269,6 +269,20 @@ def proxies_from_env() -> Dict[str, ProxyInfo]:
return ret


def get_env_proxy_for_url(url: URL) -> Tuple[URL, Optional[BasicAuth]]:
"""Get a permitted proxy for the given URL from the env."""
if url.host is not None and proxy_bypass(url.host):
raise LookupError(f"Proxying is disallowed for `{url.host!r}`")

proxies_in_env = proxies_from_env()
try:
proxy_info = proxies_in_env[url.scheme]
except KeyError:
raise LookupError(f"No proxies found for `{url!s}` in the env")
else:
return proxy_info.proxy, proxy_info.proxy_auth


@dataclasses.dataclass(frozen=True)
class MimeType:
type: str
Expand Down
71 changes: 71 additions & 0 deletions tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,77 @@ def test_proxies_from_env_http_with_auth(mocker) -> None:
assert proxy_auth.encoding == "latin1"


# --------------------- get_env_proxy_for_url ------------------------------
webknjaz marked this conversation as resolved.
Show resolved Hide resolved


@pytest.mark.parametrize(
("bypass", "proxies", "url_input", "expected_err_msg"),
(
(
True,
{"http": helpers.ProxyInfo("http://example.com", "")},
"http://aiohttp.io/path",
r"Proxying is disallowed for `'aiohttp.io'`",
),
(
False,
{"https": helpers.ProxyInfo("https://example.com", "")},
"http://aiohttp.io/path",
r"No proxies found for `http://aiohttp.io/path` in the env",
),
(
False,
{"https": helpers.ProxyInfo("https://example.com", "")},
"",
r"No proxies found for `` in the env",
),
),
ids=(
"url_matches_the_no_proxy_list",
"url_scheme_does_not_match_proxy_list",
"url_is_empty",
),
)
def test_get_env_proxy_for_url_negative(
bypass,
expected_err_msg,
mocker,
proxies,
url_input,
) -> None:
mocker.patch("aiohttp.helpers.proxy_bypass", return_value=bypass)
mocker.patch("aiohttp.helpers.proxies_from_env", return_value=proxies)
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
url = URL(url_input)
with pytest.raises(LookupError, match=expected_err_msg):
helpers.get_env_proxy_for_url(url)


@pytest.mark.parametrize(
("schema", "proxyInfo", "url"),
[
(
"https",
helpers.ProxyInfo("https://other.com", ""),
URL("https://aiohttp.com"),
),
(
"http",
helpers.ProxyInfo("http://example.com", ""),
URL("http://aiohttp.com"),
),
],
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
ids=("https", "http"),
)
def test_get_env_proxy_for_url_when_url_scheme_matches_proxy_list(
mocker, schema, proxyInfo, url
) -> None:
mocker.patch("aiohttp.helpers.proxy_bypass", return_value=False)
mocker.patch("aiohttp.helpers.proxies_from_env", return_value={schema: proxyInfo})
proxy, proxy_auth = helpers.get_env_proxy_for_url(url)
assert proxy == proxyInfo.proxy
assert proxy_auth == proxyInfo.proxy_auth


# ------------- set_result / set_exception ----------------------


Expand Down