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
@@ -229,15 +229,16 @@ 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')}
+                  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':
+        if proxy.scheme in ('https', 'wss'):
             client_logger.warning(
-                "HTTPS proxies %s are not supported, ignoring", proxy)
+                "%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
@@ -414,31 +414,27 @@ 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: