HTTP request visualizer with detailed timing breakdown (DNS → TCP → TLS → HTTP)
Project description
httptap
| Releases | CI & Analysis | Project Info |
|---|---|---|
|
|
|
|
httptap is a rich-powered CLI that dissects an HTTP request into every meaningful phase-DNS, TCP connect, TLS
handshake, server wait, and body transfer and renders the results as a timeline table, compact summary, or
machine-friendly metrics. It is designed for interactive troubleshooting, regression analysis, and recording of
performance baselines.
Highlights
- Phase-by-phase timing – precise measurements built from httpcore trace hooks (with sane fallbacks when metal-level data is unavailable).
- IPv4/IPv6 aware – the resolver and TLS inspector report both the address and its family.
- TLS insights – certificate CN, expiry countdown, cipher suite, and protocol version are captured automatically.
- Multiple output modes – rich waterfall view, compact single-line summaries, or
--metrics-onlyfor scripting. - JSON export – persist full step data (including redirect chains) for later processing.
- Extensible – clean Protocol interfaces for DNS, TLS, timing, visualization, and export so you can plug in custom behavior.
Requirements
- Python 3.13 (CPython; 3.13+ targeted as primary runtime)
- macOS, Linux, or Windows (tested on CPython)
- No system dependencies beyond standard networking
- Code must follow the Google Python Style Guide (docstrings, formatting). See Google Python Style Guide
Installation
Using uv
uv pip install httptap
From source
git clone https://github.com/ozeranskii/httptap.git
cd httptap
uv pip install .
Quick Start
Currently httptap issues a GET request and streams the entire response body. Other HTTP methods and payloads are not
supported yet; this keeps the interface simple and avoids exposing sensitive request data in output. If you need to
profile POST/PUT workloads, you can wrap httptap and override the request executor to plug in custom behavior.
Run a single request and display a rich waterfall:
httptap https://httpbin.io
Add custom headers (repeat -H for multiple values):
httptap \
-H "Accept: application/json" \
-H "Authorization: Bearer super-secret" \
https://httpbin.io/bearer
Follow redirect chains and dump metrics to JSON:
httptap --follow --json out/report.json https://httpbin.io/redirect/2
Collect compact (single-line) timings suitable for logs:
httptap --compact https://httpbin.io/get
Expose raw metrics for scripts:
httptap --metrics-only https://httpbin.io/get | tee timings.log
Releasing
Prerequisites
- GitHub Environment
pypimust be configured in repository settings - PyPI Trusted Publishing configured for
ozeranskii/httptap
Steps
- Trigger the Release workflow from GitHub Actions:
- Provide exact version (e.g.,
0.3.0), OR - Select bump type:
patch,minor, ormajor
- Provide exact version (e.g.,
- The workflow will:
- Update version in
pyproject.tomlusinguv version - Generate changelog with
git-cliffand updateCHANGELOG.md - Commit changes and create a git tag
- Run full test suite on the tagged version
- Build wheel and source distribution
- Publish to PyPI via Trusted Publishing (OIDC)
- Create GitHub Release with generated notes
- Update version in
Sample Output
The redirect summary includes a total row:
JSON Export Structure
{
"initial_url": "https://httpbin.io/redirect/2",
"total_steps": 3,
"steps": [
{
"url": "https://httpbin.io/redirect/2",
"step_number": 1,
"timing": {
"dns_ms": 8.947208058089018,
"connect_ms": 96.97712492197752,
"tls_ms": 194.56583401188254,
"ttfb_ms": 445.9513339679688,
"total_ms": 447.3437919514254,
"wait_ms": 145.46116697601974,
"xfer_ms": 1.392457983456552,
"is_estimated": false
},
"network": {
"ip": "44.211.11.205",
"ip_family": "IPv4",
"tls_version": "TLSv1.2",
"tls_cipher": "ECDHE-RSA-AES128-GCM-SHA256",
"cert_cn": "httpbin.io",
"cert_days_left": 143
},
"response": {
"status": 302,
"bytes": 0,
"content_type": null,
"server": null,
"date": "2025-10-23T19:20:36+00:00",
"location": "/relative-redirect/1",
"headers": {
"access-control-allow-credentials": "true",
"access-control-allow-origin": "*",
"location": "/relative-redirect/1",
"date": "Thu, 23 Oct 2025 19:20:36 GMT",
"content-length": "0"
}
},
"error": null,
"note": null
},
{
"url": "https://httpbin.io/relative-redirect/1",
"step_number": 2,
"timing": {
"dns_ms": 2.6895420160144567,
"connect_ms": 97.51500003039837,
"tls_ms": 193.99016606621444,
"ttfb_ms": 400.2034160075709,
"total_ms": 400.60841606464237,
"wait_ms": 106.00870789494365,
"xfer_ms": 0.4050000570714474,
"is_estimated": false
},
"network": {
"ip": "44.211.11.205",
"ip_family": "IPv4",
"tls_version": "TLSv1.2",
"tls_cipher": "ECDHE-RSA-AES128-GCM-SHA256",
"cert_cn": "httpbin.io",
"cert_days_left": 143
},
"response": {
"status": 302,
"bytes": 0,
"content_type": null,
"server": null,
"date": "2025-10-23T19:20:36+00:00",
"location": "/get",
"headers": {
"access-control-allow-credentials": "true",
"access-control-allow-origin": "*",
"location": "/get",
"date": "Thu, 23 Oct 2025 19:20:36 GMT",
"content-length": "0"
}
},
"error": null,
"note": null
},
{
"url": "https://httpbin.io/get",
"step_number": 3,
"timing": {
"dns_ms": 2.643457963131368,
"connect_ms": 97.36416593659669,
"tls_ms": 197.3062080796808,
"ttfb_ms": 403.2038329169154,
"total_ms": 403.9644579170272,
"wait_ms": 105.89000093750656,
"xfer_ms": 0.7606250001117587,
"is_estimated": false
},
"network": {
"ip": "52.70.33.41",
"ip_family": "IPv4",
"tls_version": "TLSv1.2",
"tls_cipher": "ECDHE-RSA-AES128-GCM-SHA256",
"cert_cn": "httpbin.io",
"cert_days_left": 143
},
"response": {
"status": 200,
"bytes": 389,
"content_type": "application/json; charset=utf-8",
"server": null,
"date": "2025-10-23T19:20:37+00:00",
"location": null,
"headers": {
"access-control-allow-credentials": "true",
"access-control-allow-origin": "*",
"content-type": "application/json; charset=utf-8",
"date": "Thu, 23 Oct 2025 19:20:37 GMT",
"content-length": "389"
}
},
"error": null,
"note": null
}
],
"summary": {
"total_time_ms": 1251.916665933095,
"final_status": 200,
"final_url": "https://httpbin.io/get",
"final_bytes": 389,
"errors": 0
}
}
Metrics-only scripting
httptap --metrics-only https://httpbin.io/get
Step 1: dns=30.1 connect=97.3 tls=199.0 ttfb=472.2 total=476.0 status=200 bytes=389 ip=44.211.11.205 family=IPv4
tls_version=TLSv1.2
Advanced Usage
Custom Implementations
Swap in your own resolver or TLS inspector (anything satisfying the Protocol from httptap.interfaces):
from httptap import HTTPTapAnalyzer, SystemDNSResolver
class HardcodedDNS(SystemDNSResolver):
def resolve(self, host, port, timeout):
return "93.184.216.34", "IPv4", 0.1
analyzer = HTTPTapAnalyzer(dns_resolver=HardcodedDNS())
steps = analyzer.analyze_url("https://httpbin.io")
Development
git clone https://github.com/ozeranskii/httptap.git
cd httptap
uv sync
uv run pytest
uv run ruff check
uv run ruff format .
Tests expect outbound network access; you can mock SystemDNSResolver / SocketTLSInspector when running offline.
Contributing
- Fork and clone the repo.
- Create a feature branch.
- Run
pytestandruffbefore committing. - Submit a pull request with a clear description and any relevant screenshots or benchmarks.
We welcome bug reports, feature proposals, doc improvements, and creative new visualizations or exporters.
License
Apache License 2.0 © httptap contributors. See LICENSE for details.
Acknowledgements
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file httptap-0.1.0.tar.gz.
File metadata
- Download URL: httptap-0.1.0.tar.gz
- Upload date:
- Size: 32.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f9694069a306295bb014ce67ae796637b94129884271151576f83f611558631
|
|
| MD5 |
6539392db650cb934ac0e9fd32032250
|
|
| BLAKE2b-256 |
607d3edf6aa9eef9a43b75ee978be5aa0e2e25f48a5c365689aadd4b71f8e9f1
|
Provenance
The following attestation bundles were made for httptap-0.1.0.tar.gz:
Publisher:
release.yml on ozeranskii/httptap
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
httptap-0.1.0.tar.gz -
Subject digest:
1f9694069a306295bb014ce67ae796637b94129884271151576f83f611558631 - Sigstore transparency entry: 637474255
- Sigstore integration time:
-
Permalink:
ozeranskii/httptap@5966a190575cb513b4a28783743b34742b9aac23 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ozeranskii
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5966a190575cb513b4a28783743b34742b9aac23 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file httptap-0.1.0-py3-none-any.whl.
File metadata
- Download URL: httptap-0.1.0-py3-none-any.whl
- Upload date:
- Size: 39.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7d816de880b3eed5ed238f1f928f26781216cb8cac454edcc5c49f7473c4e736
|
|
| MD5 |
9957b222660a8c92a0ed3e5437844c34
|
|
| BLAKE2b-256 |
280f1d9f2b3b2078f909b4971646284435ed6fcabb8a668135af662133cbddb3
|
Provenance
The following attestation bundles were made for httptap-0.1.0-py3-none-any.whl:
Publisher:
release.yml on ozeranskii/httptap
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
httptap-0.1.0-py3-none-any.whl -
Subject digest:
7d816de880b3eed5ed238f1f928f26781216cb8cac454edcc5c49f7473c4e736 - Sigstore transparency entry: 637474256
- Sigstore integration time:
-
Permalink:
ozeranskii/httptap@5966a190575cb513b4a28783743b34742b9aac23 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ozeranskii
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5966a190575cb513b4a28783743b34742b9aac23 -
Trigger Event:
workflow_dispatch
-
Statement type: