Hand-drives raw sockets to time each HTTP phase individually — DNS (getaddrinfo), TCP connect, TLS handshake (ssl.wrap_socket, https only), TTFB (sendall → first recv), Transfer (first byte → EOF), Total. Partial phases are preserved on failure (same invariant as Go's probe.go). Error classification mirrors Go's priority: dns → timeout → tls → connect. Output format matches Go's single-sample text layout. Input: bare URL args or plain-text config file (same format as simple.py). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2.3 KiB
2.3 KiB
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()→ DNSsock.connect()→ TCPssl.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 withhttp:///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.SSLErrororOSErrorduring TLS wrap →"tls"ConnectionRefusedError/ otherOSErrorduring 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
--timeoutflag — that's in the full version). - Bodies fully drained to get accurate Transfer timing.
Files
python/phases.py— the scriptdocs/usage/py-phases.md— user-facing doc- CHANGELOG.md entry