# Python Port — Step 2: `phases.py` ## Goal Introduce the manual per-phase timing technique — the Python answer to Go's `net/http/httptrace`. A single self-contained script that hand-drives a raw socket for each URL and times each phase individually with `time.perf_counter()`. ## Why raw sockets Python's `urllib` / `httpx` / `requests` give no per-phase callbacks, unlike Go's `httptrace.ClientTrace`. The only way to time DNS, TCP connect, TLS handshake, TTFB, and transfer independently is to drive the connection at the socket level: - `socket.getaddrinfo()` → DNS - `sock.connect()` → TCP - `ssl.SSLContext.wrap_socket()` → TLS (HTTPS only) - `sock.sendall(request)` + `sock.recv()` → TTFB - drain to EOF → Transfer ## Input - Bare URL(s) as positional args: `python phases.py https://example.com` - Or a plain-text config file: `python phases.py configs/all-ok.txt` (detected by whether the first arg starts with `http://` / `https://`) ## Output Mirrors Go's single-sample text layout for each URL: ``` https://example.com (200) DNS lookup : 18.21 ms TCP connect : 10.12 ms TLS handshake : 36.11 ms Server (TTFB) : 21.95 ms Transfer : 0.18 ms ───────────────────────────── Total : 88.00 ms ``` - TLS row omitted for `http://` URLs. - On failure: header shows `(FAILED)`, partial phases shown, error at the end. - Multiple URLs separated by a blank line. ## Error classification Mirrors Go's priority order (dns → timeout → tls → connect): - `socket.gaierror` → `"dns"` - `socket.timeout` / `TimeoutError` → `"timeout"` (regardless of phase) - `ssl.SSLError` or `OSError` during TLS wrap → `"tls"` - `ConnectionRefusedError` / other `OSError` during connect → `"connect"` - Error after first byte → `"transfer"` Partial phases are preserved on failure (same invariant as Go). ## Known limitations (documented in usage doc) - No redirect following (3xx responses are reported with their raw status code). - `Connection: close` + read-to-EOF; no keep-alive, no HTTP/2. - Timeout hard-coded at 10 s (no `--timeout` flag — that's in the full version). - Bodies fully drained to get accurate Transfer timing. ## Files - `python/phases.py` — the script - `docs/usage/py-phases.md` — user-facing doc - CHANGELOG.md entry