# aiohttp proxy lab

Loopback lab behind the ipvolt guide [aiohttp proxies in 3.14](https://ipvolt.com/guides/aiohttp-proxy-setup).
It records what aiohttp does with proxy credentials, proxy environment variables, a `socks5://` proxy URL
and an `https://` proxy URL. Everything runs on `127.0.0.1`. No proxy account is needed.

## Files

| File | What it is |
| --- | --- |
| `run_lab.py` | Starts the fixtures, runs every case in a new interpreter with a clean environment, writes `results.json` |
| `cases.py` | The cases: name, environment variables, the `python -W` value and the code |
| `auth_proxy.py` | HTTP proxy that requires Basic authentication (`user` / `pass`), supports `CONNECT` and absolute-form requests, answers 407 with `Proxy-Authenticate` otherwise, and answers non-HTTP bytes with a plaintext 400 |
| `socks5_server.py` | SOCKS5 server without authentication that logs the address type and destination of each request |
| `echo_target.py` | HTTPS target that answers with the names of the request headers it received |
| `requirements.txt` | `aiohttp==3.14.4` and `aiohttp-socks==0.12.0` |
| `results.json` | The recorded run on aiohttp 3.14.4 |
| `results-aiohttp-3.14.3.json`, `results-aiohttp-3.14.0.json`, `results-aiohttp-3.13.5.json` | The same cases on three earlier releases |

The other two targets are `python -m http.server` on port 8080 and the same server with `--tls-cert` on port
8443, which needs Python 3.14. The runner creates a two-day self-signed certificate with the `openssl` command.

## Run it

```sh
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements.txt
python run_lab.py
```

Ports 8080, 8443, 8444, 8888 and 1080 on `127.0.0.1` must be free. The runner stops if one is in use.
To record another aiohttp version, install it and pass a file name: `python run_lab.py results-other.json`.

## Reading results.json

Each case has:

- `command` and `code`: what ran. `code` follows the shared `prelude`, which is stored once at the top of the file.
- `env`: the only variables the case received besides `PATH`, `HOME` and `LAB_CA`.
- `stdout`: the status line the case printed, or `EXCEPTION <class>: <message>`.
- `stderr`: warnings and log lines. Cases run with `python -W always` unless the case name says otherwise.
- `proxy_log`: the requests the proxy received during the case, with the method, the target, whether the
  `Proxy-Authorization` header was `ok`, `wrong` or `missing`, and the names of all headers received.
- `socks_log`: what the SOCKS5 server logged during the case.

An empty `proxy_log` means the request did not go through the HTTP proxy.

## Recorded run

Recorded on 6 October 2026 on Ubuntu 26.04.1 with Python 3.14.4, OpenSSL 3.5.5, aiohttp 3.14.4,
aiohttp-socks 0.12.0 and python-socks 3.1.1. aiohttp 3.14.0 and 3.14.3 gave the same outcome in every case.
aiohttp 3.13.5 has no `encode_basic_auth` and prints no deprecation warnings.

Two things in the recorded files are not findings:

- Some HTTPS-through-proxy cases print `ResourceWarning: unclosed transport` under `-W always`. It appears when
  the interpreter exits before the TLS connection has finished closing, and it comes and goes between runs
  and versions.
- In the `socks5://` cases the bytes quoted in the error message start with `\x05\x00` and are sometimes
  followed by more bytes of the SOCKS server's reply, depending on how much had arrived. In those cases the
  SOCKS log shows a nonsense destination, because the server tried to read an HTTP request as SOCKS.

## Not covered

macOS and Windows, proxies that use Digest or NTLM authentication, authenticated SOCKS proxies, WebSocket
requests, credentials from `~/.netrc`, and proxies that are themselves reached over TLS.
