Skip to content

Advanced Features

This guide covers advanced usage patterns and customization options for httptap.

Custom DNS Resolution

You can provide custom DNS resolver implementations by using the Python API. httptap always dials the resolved IP address (IPv4/IPv6) while preserving the original hostname for the Host header and TLS SNI. IPv6 literals are bracketed automatically, so custom resolvers only need to return the correct IP/family tuple.

from httptap import HTTPTapAnalyzer, SystemDNSResolver

class CustomDNSResolver(SystemDNSResolver):
    """Custom DNS resolver with hardcoded responses."""

    def resolve(self, host: str, port: int, timeout: float):
        # Override with custom logic
        if host == "httpbin.io":
            return "44.211.11.205", "IPv4", 0.1
        return super().resolve(host, port, timeout)

# Use custom resolver
analyzer = HTTPTapAnalyzer(dns_resolver=CustomDNSResolver())
steps = analyzer.analyze_url("https://httpbin.io")

Custom TLS Inspection

Implement custom TLS inspection logic to extract additional certificate information.

from httptap import HTTPTapAnalyzer
from httptap.interfaces import TLSInspector
from httptap.models import NetworkInfo

class CustomTLSInspector:
    """Custom TLS inspector with extended certificate checks."""

    def inspect(self, host: str, port: int, timeout: float) -> NetworkInfo:
        # Custom TLS inspection logic
        # Return: NetworkInfo with TLS version, cipher, and certificate data
        ...

analyzer = HTTPTapAnalyzer(tls_inspector=CustomTLSInspector())

Programmatic Usage

Use httptap as a Python library for integration into your applications.

Basic Analysis

from httptap import HTTPTapAnalyzer

analyzer = HTTPTapAnalyzer()
steps = analyzer.analyze_url("https://httpbin.io")

for step in steps:
    print(f"URL: {step.url}")
    print(f"Status: {step.response.status}")
    print(f"Total time: {step.timing.total_ms:.2f}ms")

With Custom Headers

from httptap import HTTPTapAnalyzer

analyzer = HTTPTapAnalyzer()
headers = {
    "Authorization": "Bearer token123",
    "Accept": "application/json"
}

steps = analyzer.analyze_url(
    "https://httpbin.io/bearer",
    headers=headers
)

Following Redirects

from httptap import HTTPTapAnalyzer

analyzer = HTTPTapAnalyzer(follow_redirects=True)
steps = analyzer.analyze_url("https://httpbin.io/redirect/3")

print(f"Total steps in redirect chain: {len(steps)}")

Sending Request Body

from httptap import HTTPTapAnalyzer
from httptap.constants import HTTPMethod

analyzer = HTTPTapAnalyzer()
steps = analyzer.analyze_url(
    "https://httpbin.io/post",
    method=HTTPMethod.POST,
    content=b'{"key": "value"}',
    headers={"Content-Type": "application/json"},
)

Ignoring TLS Verification

When troubleshooting staging environments or hosts with self-signed certificates, you can skip TLS validation:

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

The request still records TLS metadata, but certificate errors are suppressed so you can focus on the protocol flow. Only use this flag in trusted environments because it disables protection against man-in-the-middle attacks. The client relaxes many cipher and protocol requirements (weak hashes, older TLS versions, small DH groups) so that legacy endpoints are more likely to complete the handshake. Extremely deprecated algorithms that OpenSSL removes entirely (e.g., RC4, 3DES on some platforms) may still fail even in this mode.

Using Proxies

Direct requests through an outbound proxy (HTTP, HTTPS, SOCKS5/SOCKS5H):

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

The Rich output and JSON export include the proxy URI when one is used.

Proxy Protocols and DNS Resolution

httptap supports four proxy protocols, each with different DNS resolution behavior:

Protocol DNS Resolved By Use Case
socks5h:// Proxy server Privacy, corporate networks, access to internal DNS
http:// Proxy server Standard HTTP proxies (CONNECT method)
https:// Proxy server Encrypted connection to proxy
socks5:// Client (local) When you need to control DNS resolution

The h suffix in socks5h stands for "hostname" (a curl convention). With socks5h://, the hostname is sent to the proxy which resolves it. With socks5://, the client resolves DNS locally and sends the IP to the proxy.

Environment Variable Proxies

When no --proxy flag is provided, httptap checks environment variables:

  1. no_proxy / NO_PROXY - Comma-separated list of hosts to bypass (lowercase takes priority)
  2. https_proxy / HTTPS_PROXY - Proxy for HTTPS requests (lowercase takes priority)
  3. http_proxy / HTTP_PROXY - Proxy for HTTP requests (lowercase takes priority)
  4. all_proxy / ALL_PROXY - Fallback proxy for all protocols

The --proxy flag always takes precedence over environment variables.

NO_PROXY patterns:

  • * - Bypass proxy for all hosts
  • example.com - Exact hostname match
  • .example.com - All subdomains of example.com
  • sub.example.com - Exact subdomain match

Custom CA Bundles

For internal endpoints signed by a private CA, supply a PEM bundle with --cacert:

httptap --cacert ~/certs/company-ca.pem https://internal-api.example.com/health

CLI output will show TLS CA: custom bundle to indicate the non-system trust store was used. JSON exports include network.tls_custom_ca: true so downstream tools can detect custom trust. The flag is mutually exclusive with --ignore-ssl.

Custom Request Executors

For fully customized behavior you can provide your own request executor. Executors receive all parameters packaged inside RequestOptions, so new flags added by httptap remain backward compatible.

from httptap import HTTPTapAnalyzer, RequestExecutor, RequestOptions, RequestOutcome


class RecordingExecutor(RequestExecutor):
    def __init__(self) -> None:
        self.last_options: RequestOptions | None = None

    def execute(self, options: RequestOptions) -> RequestOutcome:
        self.last_options = options
        # Call the built-in client (or your preferred HTTP library)
        from httptap.http_client import make_request

        timing, network, response = make_request(
            options.url,
            options.timeout,
            http2=options.http2,
            verify_ssl=options.verify_ssl,
            dns_resolver=options.dns_resolver,
            tls_inspector=options.tls_inspector,
            timing_collector=options.timing_collector,
            force_new_connection=options.force_new_connection,
            headers=options.headers,
        )
        return RequestOutcome(timing=timing, network=network, response=response)


executor = RecordingExecutor()
analyzer = HTTPTapAnalyzer(request_executor=executor)
analyzer.analyze_url("https://httpbin.io/get", headers={"X-Debug": "1"})
print(executor.last_options.headers)  # {'X-Debug': '1'}

Custom Visualization

Create your own visualization by implementing the Visualizer protocol.

from httptap.models import StepMetrics

class CustomVisualizer:
    """Custom visualizer for request steps."""

    def render(self, step: StepMetrics) -> None:
        print(f"Step {step.step_number}: {step.timing.total_ms}ms")

# Use custom visualizer
from httptap import HTTPTapAnalyzer

analyzer = HTTPTapAnalyzer()
steps = analyzer.analyze_url("https://httpbin.io")

visualizer = CustomVisualizer()
for step in steps:
    visualizer.render(step)

Custom Export Formats

Implement custom export formats beyond JSON.

from collections.abc import Sequence
from httptap.models import StepMetrics
import csv

class CSVExporter:
    """Export request data to CSV format."""

    def export(self, steps: Sequence[StepMetrics], initial_url: str, output_path: str) -> None:
        with open(output_path, "w", newline="") as f:
            writer = csv.writer(f)
            writer.writerow(["url", "status", "dns_ms", "connect_ms",
                           "tls_ms", "ttfb_ms", "total_ms"])

            for step in steps:
                writer.writerow([
                    step.url,
                    step.response.status,
                    step.timing.dns_ms,
                    step.timing.connect_ms,
                    step.timing.tls_ms,
                    step.timing.ttfb_ms,
                    step.timing.total_ms,
                ])

# Usage
from httptap import HTTPTapAnalyzer

analyzer = HTTPTapAnalyzer()
steps = analyzer.analyze_url("https://httpbin.io")

exporter = CSVExporter()
exporter.export(steps, "https://httpbin.io", "output.csv")

Performance Monitoring

Use httptap for continuous performance monitoring.

import time
from httptap import HTTPTapAnalyzer

def monitor_endpoint(url: str, interval: int = 60):
    """Monitor endpoint every interval seconds."""
    analyzer = HTTPTapAnalyzer()

    while True:
        steps = analyzer.analyze_url(url)
        step = steps[0]

        # Log metrics
        print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - "
              f"TTFB: {step.timing.ttfb_ms:.2f}ms, "
              f"Total: {step.timing.total_ms:.2f}ms, "
              f"Status: {step.response.status}")

        time.sleep(interval)

# Monitor API endpoint every minute
monitor_endpoint("https://httpbin.io/status/200", interval=60)

Batch Analysis

Analyze multiple URLs concurrently.

from concurrent.futures import ThreadPoolExecutor
from httptap import HTTPTapAnalyzer

def analyze_url(url: str):
    """Analyze a single URL."""
    analyzer = HTTPTapAnalyzer()
    steps = analyzer.analyze_url(url)
    return url, steps[0].timing.total_ms

# List of URLs to analyze
urls = [
    "https://httpbin.io",
    "https://httpbin.io/delay/1",
    "https://httpbin.io/gzip"
]

# Analyze concurrently
with ThreadPoolExecutor(max_workers=5) as executor:
    results = list(executor.map(analyze_url, urls))

# Print results
for url, total_ms in results:
    print(f"{url}: {total_ms:.2f}ms")

Error Handling

Handle errors gracefully when analyzing URLs.

from httptap import HTTPTapAnalyzer

analyzer = HTTPTapAnalyzer()
steps = analyzer.analyze_url("https://httpbin.io/status/500")

step = steps[0]
if step.has_error:
    print(f"Error: {step.error}")
else:
    print(f"Status: {step.response.status}")

Integration with Testing Frameworks

Use httptap in your test suites to verify performance requirements.

import pytest
from httptap import HTTPTapAnalyzer

def test_api_response_time():
    """Test that API responds within acceptable time."""
    analyzer = HTTPTapAnalyzer()
    steps = analyzer.analyze_url("https://httpbin.io/delay/0")

    # Assert TTFB is under 500ms
    assert steps[0].timing.ttfb_ms < 500, \
        f"TTFB too high: {steps[0].timing.ttfb_ms}ms"

    # Assert total time is under 1 second
    assert steps[0].timing.total_ms < 1000, \
        f"Total time too high: {steps[0].timing.total_ms}ms"

def test_tls_configuration():
    """Verify TLS configuration meets security standards."""
    analyzer = HTTPTapAnalyzer()
    steps = analyzer.analyze_url("https://httpbin.io")

    # Assert TLS 1.2 or higher
    assert steps[0].network.tls_version in ["TLSv1.2", "TLSv1.3"], \
        f"Insecure TLS version: {steps[0].network.tls_version}"

    # Assert certificate is valid for at least 30 days
    assert steps[0].network.cert_days_left > 30, \
        f"Certificate expiring soon: {steps[0].network.cert_days_left} days"

Environment-Specific Configuration

Configure httptap differently for various environments.

import os
from httptap import HTTPTapAnalyzer

# Environment-specific settings
config = {
    "production": {
        "timeout": 30,
        "follow_redirects": True,
    },
    "staging": {
        "timeout": 60,
        "follow_redirects": True,
    },
    "development": {
        "timeout": 120,
        "follow_redirects": False,
    },
}

env = os.getenv("ENVIRONMENT", "development")
settings = config[env]

analyzer = HTTPTapAnalyzer(
    timeout=settings["timeout"],
    follow_redirects=settings["follow_redirects"],
)
steps = analyzer.analyze_url("https://httpbin.io/status/200")

Debugging Tips

Enable Detailed Logging

import logging

# Enable debug logging
logging.basicConfig(level=logging.DEBUG)

from httptap import HTTPTapAnalyzer

analyzer = HTTPTapAnalyzer()
steps = analyzer.analyze_url("https://httpbin.io")

Inspect Raw HTTP Traffic

from httptap import HTTPTapAnalyzer

analyzer = HTTPTapAnalyzer()
steps = analyzer.analyze_url("https://httpbin.io")

# Inspect response headers
step = steps[0]
print("Response headers:")
for key, value in step.response.headers.items():
    print(f"  {key}: {value}")

What's Next?