Skip to main content

HTTP request visualizer with detailed timing breakdown (DNS → TCP → TLS → HTTP)

Project description

httptap

Releases CI & Analysis Project Info
PyPI
Python Versions
CI
CodeQL
Coverage
Build Tool
Lint
License

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).
  • All HTTP methods – GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS with request body support.
  • Request body support – send JSON, XML, or any data inline or from file with automatic Content-Type detection.
  • 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-only for 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.

📣 Exclusive for httptap users: Save 50% on GitKraken Pro. Bundle GitKraken Client, GitLens for VS Code, and powerful CLI tools to accelerate every repo workflow.


Requirements

  • Python 3.10-3.15 (CPython)
  • 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 Homebrew (macOS/Linux)

brew install httptap

Using uv

uv pip install httptap

Using pip

pip install httptap

From source

git clone https://github.com/ozeranskii/httptap.git
cd httptap
uv venv
uv pip install .

Shell completions

Homebrew Installation

If you installed httptap via Homebrew, shell completions are automatically available after installation. Just restart your shell:

# Restart your shell or reload configuration
exec $SHELL

Homebrew automatically installs completions to:

  • Bash: $(brew --prefix)/etc/bash_completion.d/
  • Zsh: $(brew --prefix)/share/zsh/site-functions/

Python Package Installation

If you installed httptap via pip or uv, you need to install the optional completion extras:

  1. Install the completion extras:

    uv pip install "httptap[completion]"
    # or
    pip install "httptap[completion]"
    
  2. Activate your virtual environment:

    source .venv/bin/activate
    
  3. Run the global activation script for argument completions:

    activate-global-python-argcomplete
    
  4. Restart your shell. Completions should now work in both bash and zsh.

Note: The global activation script provides argument completions for bash and zsh only. Other shells are not covered by the script and must be configured separately.

Usage Examples

Once completions are installed, you can use Tab to autocomplete commands and options:

# Complete command options
httptap --<TAB>
# Shows: --follow, --timeout, --no-http2, --ignore-ssl, --cacert, --proxy, --header, --compact, --metrics-only, --json, --version, --help

# Complete after typing partial option
httptap --fol<TAB>
# Completes to: httptap --follow

# Complete multiple options
httptap --follow --time<TAB>
# Completes to: httptap --follow --timeout

Quick Start

Basic GET Request

Run a single request and display a rich waterfall:

httptap https://httpbin.io/get

POST Request with Data

Send JSON data (auto-detects Content-Type):

httptap https://httpbin.io/post --data '{"name": "John", "email": "john@example.com"}'

Note: When --data is provided without --method, httptap automatically switches to POST (similar to curl).

Curl-compatible flags: httptap accepts the most common curl syntax, so you can often replace curl with httptap directly. Aliases include -X/--request for --method, -L/--location for --follow, -m/--max-time for --timeout, -k/--insecure for --ignore-ssl, -x for --proxy, and --http1.1 for --no-http2. (Not every curl option is supported—stick to these shared flags when swapping commands.)

Load data from file:

httptap https://httpbin.io/post --data @payload.json

Explicitly specify method (bypasses auto-POST):

httptap https://httpbin.io/post --method POST --data '{"status": "active"}'

Other HTTP Methods

PUT request:

httptap https://httpbin.io/put --method PUT --data '{"key": "value"}'

PATCH request:

httptap https://httpbin.io/patch --method PATCH --data '{"field": "updated"}'

DELETE request:

httptap https://httpbin.io/delete --method DELETE

Custom Headers

Add custom headers (repeat -H for multiple values):

httptap \
  -H "Accept: application/json" \
  -H "Authorization: Bearer super-secret" \
  https://httpbin.io/bearer

Redirects and JSON Export

Follow redirect chains and dump metrics to JSON:

httptap --follow --json out/report.json https://httpbin.io/redirect/2

Output Modes

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

Advanced Usage

Programmatic users can inject a custom executor for advanced scenarios. Provide your own RequestExecutor implementation if you need to change how requests are executed (for example, to plug in a different HTTP stack or add tracing).

TLS Certificate Options

Bypass TLS verification when troubleshooting self-signed endpoints:

httptap --ignore-ssl https://self-signed.badssl.com

The flag disables certificate validation and relaxes many handshake constraints so that legacy endpoints (expired/self-signed/hostname mismatches, weak hashes, older TLS versions) still complete. Some algorithms removed from modern OpenSSL builds (for example RC4 or 3DES) may remain unavailable. Use this mode only on trusted networks.

Use a custom CA certificate bundle for internal APIs:

httptap --cacert /path/to/company-ca.pem https://internal-api.company.com

This is useful when testing internal services that use certificates signed by a custom Certificate Authority (CA) that isn't in the system's default trust store. The --cacert option (also available as --ca-bundle) accepts a path to a PEM-formatted CA certificate bundle.

Note: --ignore-ssl and --cacert are mutually exclusive. Use --ignore-ssl to disable all verification, or --cacert to verify with a custom CA bundle.

When --cacert is used, the CLI output marks the connection with TLS CA: custom bundle, and JSON exports include network.tls_custom_ca: true so automation can detect custom trust configuration.

Route traffic through an HTTP/SOCKS proxy (explicit override takes precedence over env vars HTTP_PROXY, HTTPS_PROXY, NO_PROXY):

httptap --proxy socks5h://proxy.local:1080 https://httpbin.io/get

Ignore all proxy environment variables and connect directly:

httptap --proxy "" https://httpbin.io/get

The output and JSON export include the proxy URI and its source so you can confirm what path was used (e.g., (from arg --proxy), (from env HTTPS_PROXY), (bypassed by env no_proxy)).


Releasing

Prerequisites

  • GitHub Environment pypi must be configured in repository settings
  • PyPI Trusted Publishing configured for ozeranskii/httptap

Steps

  1. Trigger the Release workflow from GitHub Actions:
    • Provide exact version (e.g., 0.3.0), OR
    • Select bump type: patch, minor, or major
  2. The workflow will:
    • Update version in pyproject.toml using uv version
    • Generate changelog with git-cliff and update CHANGELOG.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

Sample Output

sample-output.png

The redirect summary includes a total row: sample-follow-redirects-output.png


JSON Export Structure

{
  "initial_url": "https://httpbin.io/redirect/2",
  "total_steps": 3,
  "steps": [
    {
      "url": "https://httpbin.io/redirect/2",
      "step_number": 1,
      "request": {
        "method": "GET",
        "headers": {},
        "body_bytes": 0
      },
      "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",
        "http_version": "HTTP/2.0",
        "tls_version": "TLSv1.2",
        "tls_cipher": "ECDHE-RSA-AES128-GCM-SHA256",
        "cert_cn": "httpbin.io",
        "cert_days_left": 143,
        "tls_verified": true,
        "tls_custom_ca": null,
        "proxy_url": null,
        "proxy_source": null
      },
      "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,
      "proxy": null
    },
    {
      "url": "https://httpbin.io/relative-redirect/1",
      "step_number": 2,
      "request": {
        "method": "GET",
        "headers": {},
        "body_bytes": 0
      },
      "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",
        "http_version": "HTTP/2.0",
        "tls_version": "TLSv1.2",
        "tls_cipher": "ECDHE-RSA-AES128-GCM-SHA256",
        "cert_cn": "httpbin.io",
        "cert_days_left": 143,
        "tls_verified": true,
        "tls_custom_ca": null,
        "proxy_url": null,
        "proxy_source": null
      },
      "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,
      "proxy": null
    },
    {
      "url": "https://httpbin.io/get",
      "step_number": 3,
      "request": {
        "method": "GET",
        "headers": {},
        "body_bytes": 0
      },
      "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",
        "http_version": "HTTP/2.0",
        "tls_version": "TLSv1.2",
        "tls_cipher": "ECDHE-RSA-AES128-GCM-SHA256",
        "cert_cn": "httpbin.io",
        "cert_days_left": 143,
        "tls_verified": true,
        "tls_custom_ca": null,
        "proxy_url": null,
        "proxy_source": null
      },
      "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,
      "proxy": 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 proxy=direct

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

  1. Fork and clone the repo.
  2. Create a feature branch.
  3. Run pytest and ruff before committing.
  4. 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 © Sergei Ozeranskii. See LICENSE for details.


Acknowledgements

  • Built on the shoulders of fantastic libraries: httpx, httpcore, and Rich.
  • Inspired by the tooling ecosystem around web performance (e.g., DevTools waterfalls, curl --trace).
  • Special thanks to everyone who opens issues, shares ideas, or contributes patches.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

httptap-0.4.5.tar.gz (42.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

httptap-0.4.5-py3-none-any.whl (51.0 kB view details)

Uploaded Python 3

File details

Details for the file httptap-0.4.5.tar.gz.

File metadata

  • Download URL: httptap-0.4.5.tar.gz
  • Upload date:
  • Size: 42.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for httptap-0.4.5.tar.gz
Algorithm Hash digest
SHA256 ddbf6f869de1959b4eae5595525da17aa21e1dbae8b81240dcc45b6a616cd74b
MD5 422d40e74c02d325e6ab809a288f8fdb
BLAKE2b-256 164e2995eb663cf92043dfbc9a186284aea4085d2612d822bb03d8055796bbcc

See more details on using hashes here.

Provenance

The following attestation bundles were made for httptap-0.4.5.tar.gz:

Publisher: release.yml on ozeranskii/httptap

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file httptap-0.4.5-py3-none-any.whl.

File metadata

  • Download URL: httptap-0.4.5-py3-none-any.whl
  • Upload date:
  • Size: 51.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for httptap-0.4.5-py3-none-any.whl
Algorithm Hash digest
SHA256 58261143f7fa9ab3d69ba530857b94ce197252e3cba52712a0ead0f0c83f0220
MD5 845b37cee338ce5ac4010861c5373d98
BLAKE2b-256 f72fb8ad52a5a564c8a34c5065b77d2f38d6ce10ef0e2fc7d5b362de783f1b36

See more details on using hashes here.

Provenance

The following attestation bundles were made for httptap-0.4.5-py3-none-any.whl:

Publisher: release.yml on ozeranskii/httptap

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page