Repository: encode/httpx

Issue:
ASGI requests are routed through proxy
### Checklist

- [X] The bug is reproducible against the latest release and/or `master`.
- [X] There are no similar issues or pull requests to fix it yet.

### Describe the bug

Using the `AsyncClient` with an ASGI app seems to go through the proxy. Either this is is bug or I don't understand under which conditions this would make sense.

### To reproduce

```python
import asyncio
import os
import httpx


async def hello_world(scope, receive, send):
    status = 200
    output = b"Hello, World!"
    headers = [(b"content-type", "text/plain"), (b"content-length", str(len(output)))]

    await send({"type": "http.response.start", "status": status, "headers": headers})
    await send({"type": "http.response.body", "body": output})


async def test_asgi_with_env():
    os.environ["http_proxy"] = "http://localhost:1111"

    client = httpx.AsyncClient(app=hello_world)
    response = await client.get("http://www.example.org/")
    assert response.status_code == 200
    assert response.text == "Hello, World!"


asyncio.run(test_asgi_with_env())
```

### Expected behavior

Test should pass, no proxy interaction should be made.

Note that passing `trust_env=False` to `AsyncClient` disables the proxy and the test passes. However, this interaction seems weird.

### Actual behavior

```text
httpcore._exceptions.ConnectError: Multiple exceptions: [Errno 61] Connect call failed ('::1', 1111, 0, 0), [Errno 61] Connect call failed ('127.0.0.1', 1111
```

### Environment

- OS: macOS
- Python version: 3.8.3
- HTTPX version: 0.13.2 and on master (`cfd07cc290cc610fceb8d9f025c6bebc1d4b3275`)
- Async environment: asyncio, but also tested with trio where the issue also occurs.
- HTTP proxy: see description
- Custom certificates: no

### Additional context
https://github.com/tiangolo/fastapi/pull/1619


Discussion:
Might be worth looking if there’s any past discussion about whether we should make `trust_env` default to `False`... I think we’ve had strange UX like these in the past due to HTTPX using env cars when it didn’t really make sense.

Alternatively, on a smaller scope, we can look at the auto proxies setup code, see if it makes sense to bypass it if an `app` was passed... (Noting that it’s fixes the behavior in this particular case only, eg custom dispatchers where mount-by-scheme doesn’t really make sense could also experience this bug in the future. But you know, YAGNI...)
> on a smaller scope, we can look at the auto proxies setup code, see if it makes sense to bypass it if an app was passed...

Yup absolutely makes sense to ignore the proxies setup if `app=...` is passed. 👍
Cool, looks like the resolution path is all rounded there, up for grabs…

Resolving patch:
diff --git a/httpx/_client.py b/httpx/_client.py
--- a/httpx/_client.py
+++ b/httpx/_client.py
@@ -89,10 +89,10 @@ def trust_env(self) -> bool:
         return self._trust_env
 
     def _get_proxy_map(
-        self, proxies: typing.Optional[ProxiesTypes], trust_env: bool,
+        self, proxies: typing.Optional[ProxiesTypes], allow_env_proxies: bool,
     ) -> typing.Dict[str, typing.Optional[Proxy]]:
         if proxies is None:
-            if trust_env:
+            if allow_env_proxies:
                 return {
                     key: None if url is None else Proxy(url=url)
                     for key, url in get_environment_proxies().items()
@@ -473,7 +473,8 @@ def __init__(
             )
             limits = pool_limits
 
-        proxy_map = self._get_proxy_map(proxies, trust_env)
+        allow_env_proxies = trust_env and app is None and transport is None
+        proxy_map = self._get_proxy_map(proxies, allow_env_proxies)
 
         self._transport = self._init_transport(
             verify=verify,
@@ -1003,7 +1004,8 @@ def __init__(
             )
             limits = pool_limits
 
-        proxy_map = self._get_proxy_map(proxies, trust_env)
+        allow_env_proxies = trust_env and app is None and transport is None
+        proxy_map = self._get_proxy_map(proxies, allow_env_proxies)
 
         self._transport = self._init_transport(
             verify=verify,


Test patch: