Repository: aio-libs/aiohttp

Issue:
ws_connect doesn't follow proxy from environment
🐞 **Describe the bug**
<!-- A clear and concise description of what the bug is, on the next line. -->
I have set both http_proxy and https_proxy, with trust_env=True when create ClientSession,
everything is ok when get or post with this session, but for the ws_connect, It doesn't run with proxy.

💡 **To Reproduce**
<!-- How to reproduce the behavior? 
For example:
1. Have certain environment
2. Run given code snippet in a certain way
3. See some behavior described

Add these steps below this comment: -->
background: I cann't visit wss://stream.binance.com:9443/ws under my network but it's ok with proxy
1. prepare a proxy at port 1086
1. http_proxy="http://127.0.0.1:1086"
2. https_proxy="https://127.0.0.1:1086"

``` python
import asyncio
import aiohttp
async def main():
    async with aiohttp.ClientSession(trust_env=True) as client:
        await client.ws_connect('wss://stream.binance.com:9443/ws')
        print('ok')

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

```
and it block forever (with a tcp handshake timeout exception)


💡 **Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
just connect with proxy

📋 **Logs/tracebacks**
<!-- If applicable, add logs/tracebacks to help explain your problem. -->
```python-traceback (paste your traceback in the next line)

```

📋 **Your version of the Python**
<!-- Attach your version of the Python. -->
```console
$ python --version
Python 3.7.6
```

📋 **Your version of the aiohttp/yarl/multidict distributions**
<!-- Attach your version of the distributions in the code blocks below. -->
```console
$ python -m pip show aiohttp
Version: 3.6.2
```
```console
$ python -m pip show multidict
Version: 4.7.5
```
```console
$ python -m pip show yarl
Version: 1.1.0
```

📋 **Additional context**
<!-- Add any other context about the problem here, in the next line. -->
https://github.com/aio-libs/aiohttp/blob/bb7a4eaf21782612cc4c24fa75b9368ddb204cc7/aiohttp/client.py#L426
scheme of websocket is ws or wss, url.scheme will always be http or https and ignore any other proxy here 
https://github.com/aio-libs/aiohttp/blob/bb7a4eaf21782612cc4c24fa75b9368ddb204cc7/aiohttp/helpers.py#L232

but it's ok for ws/wss to use http proxy
<!-- Describe the environment you have that lead to your issue.
     This includes aiohttp version, OS, proxy server and other bits that
     are related to your case.

     IMPORTANT: aiohttp is both server framework and client library.
     For getting rid of confusing please put 'server', 'client' or 'both'
     word here.
     -->



Discussion:
Feel free to send a PR with tests bundled.

Resolving patch:
diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py
--- a/aiohttp/helpers.py
+++ b/aiohttp/helpers.py
@@ -240,14 +240,20 @@ class ProxyInfo:
 
 
 def proxies_from_env() -> Dict[str, ProxyInfo]:
-    proxy_urls = {k: URL(v) for k, v in getproxies().items() if k in ("http", "https")}
+    proxy_urls = {
+        k: URL(v)
+        for k, v in getproxies().items()
+        if k in ("http", "https", "ws", "wss")
+    }
     netrc_obj = netrc_from_env()
     stripped = {k: strip_auth_from_url(v) for k, v in proxy_urls.items()}
     ret = {}
     for proto, val in stripped.items():
         proxy, auth = val
-        if proxy.scheme == "https":
-            client_logger.warning("HTTPS proxies %s are not supported, ignoring", proxy)
+        if proxy.scheme in ("https", "wss"):
+            client_logger.warning(
+                "%s proxies %s are not supported, ignoring", proxy.scheme.upper(), proxy
+            )
             continue
         if netrc_obj and auth is None:
             auth_from_netrc = None


Test patch:
diff --git a/tests/test_helpers.py b/tests/test_helpers.py
--- a/tests/test_helpers.py
+++ b/tests/test_helpers.py
@@ -462,32 +462,26 @@ def test_set_content_disposition_bad_param() -> None:
 # --------------------- proxies_from_env ------------------------------
 
 
-def test_proxies_from_env_http(mocker) -> None:
+@pytest.mark.parametrize("protocol", ["http", "https", "ws", "wss"])
+def test_proxies_from_env(monkeypatch, protocol) -> None:
     url = URL("http://aiohttp.io/path")
-    mocker.patch.dict(os.environ, {"http_proxy": str(url)})
-    ret = helpers.proxies_from_env()
-    assert ret.keys() == {"http"}
-    assert ret["http"].proxy == url
-    assert ret["http"].proxy_auth is None
-
-
-def test_proxies_from_env_http_proxy_for_https_proto(mocker) -> None:
-    url = URL("http://aiohttp.io/path")
-    mocker.patch.dict(os.environ, {"https_proxy": str(url)})
+    monkeypatch.setenv(protocol + "_proxy", str(url))
     ret = helpers.proxies_from_env()
-    assert ret.keys() == {"https"}
-    assert ret["https"].proxy == url
-    assert ret["https"].proxy_auth is None
+    assert ret.keys() == {protocol}
+    assert ret[protocol].proxy == url
+    assert ret[protocol].proxy_auth is None
 
 
-def test_proxies_from_env_https_proxy_skipped(mocker) -> None:
-    url = URL("https://aiohttp.io/path")
-    mocker.patch.dict(os.environ, {"https_proxy": str(url)})
-    log = mocker.patch("aiohttp.log.client_logger.warning")
+@pytest.mark.parametrize("protocol", ["https", "wss"])
+def test_proxies_from_env_skipped(monkeypatch, caplog, protocol) -> None:
+    url = URL(protocol + "://aiohttp.io/path")
+    monkeypatch.setenv(protocol + "_proxy", str(url))
     assert helpers.proxies_from_env() == {}
-    log.assert_called_with(
-        "HTTPS proxies %s are not supported, ignoring", URL("https://aiohttp.io/path")
+    assert len(caplog.records) == 1
+    log_message = "{proto!s} proxies {url!s} are not supported, ignoring".format(
+        proto=protocol.upper(), url=url
     )
+    assert caplog.record_tuples == [("aiohttp.client", 30, log_message)]
 
 
 def test_proxies_from_env_http_with_auth(mocker) -> None: