Skip to content

OneNobleSoul/reqphase

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

reqphase

Timing breakdown of an HTTP(S) request: DNS lookup, TCP connect, TLS handshake, time to first byte, content transfer.

Install

pip install -e .

Python 3.11+, no runtime dependencies.

Usage

reqphase https://example.com
HTTP/1.1 200 OK
559 bytes

  DNS Lookup              4.1ms  ###
  TCP Connect             1.5ms  #
  TLS Handshake           5.6ms  ####
  Server Processing      46.9ms  ################################
  Content Transfer        0.2ms  #

  Total                  72.4ms

Bars are scaled relative to the slowest phase in that particular request, not to a fixed axis, so a single request's chart is easy to read at a glance.

A bare host without a scheme is treated as http://:

reqphase example.com:8080

-k/--insecure skips TLS certificate verification (self-signed certs, internal CAs). --timeout sets the socket timeout in seconds (default 10). --no-color disables ANSI output for piping into a file or another tool.

Machine-readable output for scripts:

reqphase --json https://example.com | jq .timings_ms
{
  "dns_lookup": 4.1,
  "tcp_connect": 1.5,
  "tls_handshake": 5.6,
  "server_processing": 46.9,
  "content_transfer": 0.2,
  "total": 72.4
}

-L/--location follows redirects instead of stopping at the first 3xx. Every hop is measured, but only the final response gets the full phase breakdown; the hops in between are summarised above it:

  301 http://github.com -> https://github.com/

HTTP/1.1 200 OK
...

With --json, the hops show up under a redirects key instead. --max-redirects (default 5) caps how many hops it will chase before giving up with an error.

Exit codes

code meaning
0 got a response (any status code)
2 usage error (bad arguments)
3 request failed - DNS, connect, TLS, timeout

reqphase doesn't turn a 4xx/5xx response into a failing exit code by itself; it measured a real response, which is a success from its point of view. Check the status in the output if you need that.

How the phases are measured

There's no curl subprocess and no requests underneath. reqphase resolves the host itself (socket.getaddrinfo), opens the TCP connection itself, does the TLS handshake itself if the URL is https, and then speaks just enough HTTP/1.1 by hand to send the request line and read back a status line, headers and body. Every one of those steps gets its own timestamp, which is what makes the phase breakdown possible: server processing ends the moment the first byte of the response arrives, content transfer is everything after that until the body is fully read (respecting Content-Length or chunked encoding, falling back to reading until the connection closes).

Why

I wanted the old httpstat output on a machine where I didn't want to pull in curl or fight its -w template syntax, and doing the socket-level timing by hand felt like a more interesting weekend than reaching for requests.

Prior art

The idea and the shape of the output (phase-by-phase timing with a small bar chart) come from httpstat, which wraps curl's -w timing variables and has been without a release since 2020. reqphase is a clean-room reimplementation: no code was carried over, and the approach is different underneath - stdlib sockets instead of shelling out to curl. Attribution and thanks to the original for making the idea obvious in the first place.

Scope

One request, one process, HTTP/1.1 only. No HTTP/2, no connection reuse across requests, no request bodies (GET-shaped tools only, so no -d/-X flags). If you need any of that, curl's -w output or a proper HTTP client is the better tool; reqphase stays small on purpose.

Development

pip install -e ".[dev]"
pytest
ruff check .

License

MIT. See LICENSE.

About

Timing breakdown of an HTTP(S) request: DNS, connect, TLS, TTFB, transfer

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages