Saltar a contenido

Core Components

This page documents the core class and data models of httptap, rendered directly from the source docstrings so signatures and defaults always match the installed version.

HTTPTapAnalyzer

The main analyzer class that orchestrates HTTP request analysis, including redirect following and per-step metric collection.

from httptap import HTTPTapAnalyzer
from httptap.constants import HTTPMethod

analyzer = HTTPTapAnalyzer(follow_redirects=True)
steps = analyzer.analyze_url(
    "https://httpbin.io",
    headers={"Accept": "application/json"},
)

HTTPTapAnalyzer

HTTPTapAnalyzer(
    *,
    follow_redirects: bool = False,
    timeout: float = DEFAULT_TIMEOUT_SECONDS,
    http2: bool = True,
    verify_ssl: bool = True,
    ca_bundle_path: str | None = None,
    max_redirects: int = 10,
    request_executor: RequestExecutor | None = None,
    proxy: ProxyTypes | None = None,
    noproxy: bool = False,
    dns_resolver: DNSResolver | None = None,
    tls_inspector: TLSInspector | None = None,
    timing_collector_factory: type[TimingCollector]
    | None = None,
)

Orchestrates HTTP request analysis with redirect following.

This class manages the high-level flow of analyzing HTTP requests, including following redirect chains and collecting metrics at each step.

属性:

  • follow_redirects

    Whether to follow HTTP redirects.

  • timeout

    Request timeout in seconds.

  • http2

    Whether to enable HTTP/2 support.

  • max_redirects

    Maximum number of redirects to follow.

引数:

  • follow_redirects (bool, デフォルト: False ) –

    Whether to follow 3xx redirects.

  • timeout (float, デフォルト: DEFAULT_TIMEOUT_SECONDS ) –

    Request timeout in seconds.

  • http2 (bool, デフォルト: True ) –

    Enable HTTP/2 support.

  • verify_ssl (bool, デフォルト: True ) –

    Whether to verify TLS certificates.

  • ca_bundle_path (str | None, デフォルト: None ) –

    Path to custom CA certificate bundle (PEM format). Only used when verify_ssl is True. If None, uses system CA bundle.

  • max_redirects (int, デフォルト: 10 ) –

    Maximum number of redirects to follow.

  • request_executor (RequestExecutor | None, デフォルト: None ) –

    Object responsible for performing HTTP requests. Must implement the RequestExecutor protocol. Defaults to the built-in httpx implementation.

  • proxy (ProxyTypes | None, デフォルト: None ) –

    Optional proxy URL (http/https/socks5/socks5h) applied to all requests in the analysis chain.

  • noproxy (bool, デフォルト: False ) –

    When True, ignore proxy environment variables and connect directly. Triggered by --proxy "".

  • dns_resolver (DNSResolver | None, デフォルト: None ) –

    Custom DNS resolver implementation. If None, make_request will use its default (SystemDNSResolver).

  • tls_inspector (TLSInspector | None, デフォルト: None ) –

    Custom TLS inspector implementation. If None, make_request will use its default (SocketTLSInspector).

  • timing_collector_factory (type[TimingCollector] | None, デフォルト: None ) –

    Factory class for creating timing collectors. If None, make_request will use its default (PerfCounterTimingCollector). Note: This should be a class, not an instance, as a new collector is created for each request.

メソッド:

  • analyze_url

    Analyze URL with optional redirect following.

ソースコード位置: httptap/analyzer.py
def __init__(  # noqa: PLR0913
    self,
    *,
    follow_redirects: bool = False,
    timeout: float = DEFAULT_TIMEOUT_SECONDS,
    http2: bool = True,
    verify_ssl: bool = True,
    ca_bundle_path: str | None = None,
    max_redirects: int = 10,
    request_executor: RequestExecutor | None = None,
    proxy: ProxyTypes | None = None,
    noproxy: bool = False,
    dns_resolver: DNSResolver | None = None,
    tls_inspector: TLSInspector | None = None,
    timing_collector_factory: type[TimingCollector] | None = None,
) -> None:
    """Initialize HTTP analyzer.

    Args:
        follow_redirects: Whether to follow 3xx redirects.
        timeout: Request timeout in seconds.
        http2: Enable HTTP/2 support.
        verify_ssl: Whether to verify TLS certificates.
        ca_bundle_path: Path to custom CA certificate bundle (PEM format).
            Only used when verify_ssl is True. If None, uses system CA bundle.
        max_redirects: Maximum number of redirects to follow.
        request_executor: Object responsible for performing HTTP requests.
            Must implement the RequestExecutor protocol. Defaults to the
            built-in httpx implementation.
        proxy: Optional proxy URL (http/https/socks5/socks5h) applied to all
            requests in the analysis chain.
        noproxy: When True, ignore proxy environment variables and connect
            directly. Triggered by --proxy "".
        dns_resolver: Custom DNS resolver implementation. If None, make_request
            will use its default (SystemDNSResolver).
        tls_inspector: Custom TLS inspector implementation. If None, make_request
            will use its default (SocketTLSInspector).
        timing_collector_factory: Factory class for creating timing collectors.
            If None, make_request will use its default (PerfCounterTimingCollector).
            Note: This should be a class, not an instance, as a new collector
            is created for each request.

    """
    self.follow_redirects = follow_redirects
    self.timeout = timeout
    self.http2 = http2
    self.verify_ssl = verify_ssl
    self.ca_bundle_path = ca_bundle_path
    self.max_redirects = max_redirects
    self._request = request_executor or HTTPClientRequestExecutor()
    self._dns_resolver = dns_resolver
    self._tls_inspector = tls_inspector
    self._timing_collector = timing_collector_factory
    self._proxy = proxy
    self._noproxy = noproxy

analyze_url

analyze_url(
    url: str,
    *,
    method: HTTPMethod = GET,
    content: bytes | None = None,
    headers: Mapping[str, str] | None = None,
) -> list[StepMetrics]

Analyze URL with optional redirect following.

Performs HTTP request(s) and collects comprehensive metrics. If follow_redirects is enabled and server returns 3xx with Location, continues following redirects up to max_redirects.

引数:

  • url (str) –

    Initial URL to analyze. Must be valid HTTP/HTTPS URL.

  • method (HTTPMethod, デフォルト: GET ) –

    HTTP method to use (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS).

  • content (bytes | None, デフォルト: None ) –

    Optional request body as bytes.

  • headers (Mapping[str, str] | None, デフォルト: None ) –

    Optional mapping of request headers applied to every step.

戻り値:

  • list[StepMetrics]

    List of StepMetrics, one per request in the chain. Each step contains

  • list[StepMetrics]

    timing, network, and response information. Returns at least one step

  • list[StepMetrics]

    even if request fails.

例:

Basic usage without redirects: >>> analyzer = HTTPTapAnalyzer() >>> steps = analyzer.analyze_url("https://example.com") >>> print(f"Total time: {steps[0].timing.total_ms}ms") Total time: 234.5ms

Following redirect chain: >>> analyzer = HTTPTapAnalyzer(follow_redirects=True) >>> steps = analyzer.analyze_url("http://example.com") >>> for i, step in enumerate(steps, 1): ... print(f"Step {i}: {step.response.status}") Step 1: 301 Step 2: 200

ソースコード位置: httptap/analyzer.py
def analyze_url(
    self,
    url: str,
    *,
    method: HTTPMethod = HTTPMethod.GET,
    content: bytes | None = None,
    headers: Mapping[str, str] | None = None,
) -> list[StepMetrics]:
    """Analyze URL with optional redirect following.

    Performs HTTP request(s) and collects comprehensive metrics.
    If follow_redirects is enabled and server returns 3xx with Location,
    continues following redirects up to max_redirects.

    Args:
        url: Initial URL to analyze. Must be valid HTTP/HTTPS URL.
        method: HTTP method to use (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS).
        content: Optional request body as bytes.
        headers: Optional mapping of request headers applied to every step.

    Returns:
        List of StepMetrics, one per request in the chain. Each step contains
        timing, network, and response information. Returns at least one step
        even if request fails.

    Examples:
        Basic usage without redirects:
            >>> analyzer = HTTPTapAnalyzer()
            >>> steps = analyzer.analyze_url("https://example.com")
            >>> print(f"Total time: {steps[0].timing.total_ms}ms")
            Total time: 234.5ms

        Following redirect chain:
            >>> analyzer = HTTPTapAnalyzer(follow_redirects=True)
            >>> steps = analyzer.analyze_url("http://example.com")
            >>> for i, step in enumerate(steps, 1):
            ...     print(f"Step {i}: {step.response.status}")
            Step 1: 301
            Step 2: 200

    """
    steps: list[StepMetrics] = []
    current_url = url
    redirect_count = 0

    while redirect_count <= self.max_redirects:
        step_number = len(steps) + 1
        step = self._analyze_single_request(
            current_url,
            step_number,
            method=method,
            content=content,
            headers=headers,
        )
        steps.append(step)

        # Check if we should follow redirect
        if not self.follow_redirects:
            break

        if step.has_error:
            # Stop on error
            break

        if step.is_redirect:
            # Follow redirect
            next_url = step.response.location
            if next_url:
                # Handle relative URLs
                current_url = urljoin(current_url, next_url)
                redirect_count += 1
            else:
                # No Location header despite 3xx status
                break
        else:
            # Not a redirect, we're done
            break

    return steps

Data models

All models are @dataclass(slots=True) and expose to_dict() for JSON export.

StepMetrics dataclass

StepMetrics(
    url: str = "",
    step_number: int = 1,
    timing: TimingMetrics = TimingMetrics(),
    network: NetworkInfo = NetworkInfo(),
    response: ResponseInfo = ResponseInfo(),
    error: str | None = None,
    note: str | None = None,
    proxied_via: str | None = None,
    request_method: str | None = None,
    request_headers: dict[str, str] = dict(),
    request_body_bytes: int = 0,
)

Complete metrics for a single HTTP request step.

Represents all collected data for one request in the chain, including timing, network, response information, and any errors.

属性:

  • url (str) –

    The URL that was requested.

  • step_number (int) –

    Step number in redirect chain (1-indexed).

  • timing (TimingMetrics) –

    Timing metrics.

  • network (NetworkInfo) –

    Network and security information.

  • response (ResponseInfo) –

    HTTP response information.

  • error (str | None) –

    Error message if request failed.

  • note (str | None) –

    Additional notes or context.

  • proxied_via (str | None) –

    Proxy URL used for this request, if any.

  • request_method (str | None) –

    HTTP method used (GET, POST, PUT, etc.).

  • request_headers (dict[str, str]) –

    Request headers (sanitized).

  • request_body_bytes (int) –

    Size of request body in bytes.

メソッド:

  • to_dict

    Convert step metrics to dictionary for JSON export.

has_error property

has_error: bool

Check if this step encountered an error.

戻り値:

  • bool

    True if error occurred, False otherwise.

is_redirect property

is_redirect: bool

Check if this step is a redirect response.

戻り値:

  • bool

    True if status is 3xx and Location header present.

to_dict

to_dict() -> dict[str, Any]

Convert step metrics to dictionary for JSON export.

戻り値:

  • dict[str, Any]

    Dictionary containing all step information organized by category.

ソースコード位置: httptap/models.py
def to_dict(self) -> dict[str, Any]:
    """Convert step metrics to dictionary for JSON export.

    Returns:
        Dictionary containing all step information organized by category.

    """
    return {
        "url": self.url,
        "step_number": self.step_number,
        "request": {
            "method": self.request_method,
            "headers": self.request_headers,
            "body_bytes": self.request_body_bytes,
        },
        "timing": self.timing.to_dict(),
        "network": self.network.to_dict(),
        "response": self.response.to_dict(),
        "error": self.error,
        "note": self.note,
        "proxy": self.proxied_via,
    }

TimingMetrics dataclass

TimingMetrics(
    dns_ms: float = 0.0,
    connect_ms: float = 0.0,
    tls_ms: float = 0.0,
    ttfb_ms: float = 0.0,
    total_ms: float = 0.0,
    wait_ms: float = 0.0,
    xfer_ms: float = 0.0,
    is_estimated: bool = False,
)

Timing metrics for HTTP request phases.

All timing values are in milliseconds.

属性:

  • dns_ms (float) –

    DNS resolution time.

  • connect_ms (float) –

    TCP connection establishment time.

  • tls_ms (float) –

    TLS handshake time (0 for HTTP).

  • ttfb_ms (float) –

    Time to first byte (headers received).

  • total_ms (float) –

    Total request time from start to finish.

  • wait_ms (float) –

    Server processing time (derived metric).

  • xfer_ms (float) –

    Response body transfer time (derived metric).

  • is_estimated (bool) –

    Whether connect/TLS timing was estimated vs measured.

メソッド:

calculate_derived

calculate_derived() -> None

Calculate derived timing metrics.

This method computes timing values that are derived from the raw measurements. It should be called after all raw timing values (dns_ms, connect_ms, tls_ms, ttfb_ms, total_ms) are populated.

Computes

wait_ms: Time server spent processing the request before sending the first byte of the response. Calculated as: max(0, ttfb_ms - (dns_ms + connect_ms + tls_ms))

xfer_ms: Time spent transferring the response body after headers were received. Calculated as: max(0, total_ms - ttfb_ms)

Note

Both values are clamped to 0 to handle edge cases where timing measurements may have slight inconsistencies due to measurement precision or system clock adjustments.

例:

>>> timing = TimingMetrics(
...     dns_ms=10.0,
...     connect_ms=20.0,
...     tls_ms=50.0,
...     ttfb_ms=100.0,
...     total_ms=150.0
... )
>>> timing.calculate_derived()
>>> print(f"Wait: {timing.wait_ms}ms, Transfer: {timing.xfer_ms}ms")
Wait: 20.0ms, Transfer: 50.0ms
ソースコード位置: httptap/models.py
def calculate_derived(self) -> None:
    """Calculate derived timing metrics.

    This method computes timing values that are derived from the raw
    measurements. It should be called after all raw timing values
    (dns_ms, connect_ms, tls_ms, ttfb_ms, total_ms) are populated.

    Computes:
        wait_ms: Time server spent processing the request before sending
            the first byte of the response. Calculated as:
            max(0, ttfb_ms - (dns_ms + connect_ms + tls_ms))

        xfer_ms: Time spent transferring the response body after headers
            were received. Calculated as:
            max(0, total_ms - ttfb_ms)

    Note:
        Both values are clamped to 0 to handle edge cases where timing
        measurements may have slight inconsistencies due to measurement
        precision or system clock adjustments.

    Examples:
        >>> timing = TimingMetrics(
        ...     dns_ms=10.0,
        ...     connect_ms=20.0,
        ...     tls_ms=50.0,
        ...     ttfb_ms=100.0,
        ...     total_ms=150.0
        ... )
        >>> timing.calculate_derived()
        >>> print(f"Wait: {timing.wait_ms}ms, Transfer: {timing.xfer_ms}ms")
        Wait: 20.0ms, Transfer: 50.0ms

    """
    self.wait_ms = max(
        0.0,
        self.ttfb_ms - (self.dns_ms + self.connect_ms + self.tls_ms),
    )
    self.xfer_ms = max(0.0, self.total_ms - self.ttfb_ms)

to_dict

to_dict() -> dict[str, float | bool]

Convert timing metrics to dictionary.

戻り値:

  • dict[str, float | bool]

    Dictionary with all timing metrics.

ソースコード位置: httptap/models.py
def to_dict(self) -> dict[str, float | bool]:
    """Convert timing metrics to dictionary.

    Returns:
        Dictionary with all timing metrics.

    """
    return {
        "dns_ms": self.dns_ms,
        "connect_ms": self.connect_ms,
        "tls_ms": self.tls_ms,
        "ttfb_ms": self.ttfb_ms,
        "total_ms": self.total_ms,
        "wait_ms": self.wait_ms,
        "xfer_ms": self.xfer_ms,
        "is_estimated": self.is_estimated,
    }

NetworkInfo dataclass

NetworkInfo(
    ip: str | None = None,
    ip_family: str | None = None,
    http_version: str | None = None,
    tls_version: str | None = None,
    tls_cipher: str | None = None,
    cert_cn: str | None = None,
    cert_days_left: int | None = None,
    cert_sans: list[str] = list(),
    cert_issuer: str | None = None,
    cert_serial: str | None = None,
    cert_not_before: datetime | None = None,
    cert_not_after: datetime | None = None,
    tls_verified: bool | None = None,
    tls_custom_ca: bool | None = None,
    proxy_url: str | None = None,
    proxy_source: str | None = None,
)

Network and security information for a connection.

属性:

  • ip (str | None) –

    Resolved IP address.

  • ip_family (str | None) –

    Address family label (e.g., 'IPv4' or 'IPv6').

  • http_version (str | None) –

    HTTP protocol version negotiated (e.g., 'HTTP/2.0').

  • tls_version (str | None) –

    TLS protocol version (e.g., 'TLSv1.3').

  • tls_cipher (str | None) –

    TLS cipher suite used.

  • cert_cn (str | None) –

    Certificate Common Name.

  • cert_days_left (int | None) –

    Days until certificate expiration.

  • cert_sans (list[str]) –

    Subject Alternative Names (DNS entries) from the leaf certificate.

  • cert_issuer (str | None) –

    Issuer Common Name of the leaf certificate.

  • cert_serial (str | None) –

    Certificate serial number (hex string).

  • cert_not_before (datetime | None) –

    Start of the certificate validity window.

  • cert_not_after (datetime | None) –

    End of the certificate validity window.

  • tls_verified (bool | None) –

    Whether TLS certificate verification was enforced.

  • tls_custom_ca (bool | None) –

    True when a custom CA bundle was configured.

  • proxy_url (str | None) –

    Effective proxy URL used for this request, or None.

  • proxy_source (str | None) –

    Origin of the proxy setting (see PROXY_SOURCE_* constants). None when no proxy is configured.

メソッド:

  • to_dict

    Convert network info to dictionary.

to_dict

to_dict() -> dict[str, Any]

Convert network info to dictionary.

戻り値:

  • dict[str, Any]

    Dictionary with all network information.

ソースコード位置: httptap/models.py
def to_dict(self) -> dict[str, Any]:
    """Convert network info to dictionary.

    Returns:
        Dictionary with all network information.

    """
    return {
        "ip": self.ip,
        "ip_family": self.ip_family,
        "http_version": self.http_version,
        "tls_version": self.tls_version,
        "tls_cipher": self.tls_cipher,
        "cert_cn": self.cert_cn,
        "cert_days_left": self.cert_days_left,
        "cert_sans": self.cert_sans,
        "cert_issuer": self.cert_issuer,
        "cert_serial": self.cert_serial,
        "cert_not_before": self.cert_not_before.isoformat() if self.cert_not_before else None,
        "cert_not_after": self.cert_not_after.isoformat() if self.cert_not_after else None,
        "tls_verified": self.tls_verified,
        "tls_custom_ca": self.tls_custom_ca,
        "proxy_url": self.proxy_url,
        "proxy_source": self.proxy_source,
    }

ResponseInfo dataclass

ResponseInfo(
    status: int | None = None,
    bytes: int = 0,
    content_type: str | None = None,
    server: str | None = None,
    date: datetime | None = None,
    location: str | None = None,
    headers: dict[str, str] = dict(),
)

HTTP response information.

属性:

  • status (int | None) –

    HTTP status code.

  • bytes (int) –

    Response body size in bytes.

  • content_type (str | None) –

    Content-Type header value.

  • server (str | None) –

    Server header value.

  • date (datetime | None) –

    Date header parsed as datetime.

  • location (str | None) –

    Location header for redirects.

  • headers (dict[str, str]) –

    Sanitized response headers (secrets masked).

メソッド:

  • to_dict

    Convert response info to dictionary.

to_dict

to_dict() -> dict[str, Any]

Convert response info to dictionary.

戻り値:

  • dict[str, Any]

    Dictionary with all response information.

ソースコード位置: httptap/models.py
def to_dict(self) -> dict[str, Any]:
    """Convert response info to dictionary.

    Returns:
        Dictionary with all response information.

    """
    return {
        "status": self.status,
        "bytes": self.bytes,
        "content_type": self.content_type,
        "server": self.server,
        "date": self.date.isoformat() if self.date else None,
        "location": self.location,
        "headers": self.headers,
    }

Utility functions

validate_url

validate_url(url: str) -> bool

Validate URL format.

Requires an http/https scheme and a non-empty host. A scheme alone is not enough: https:///path and https://?query parse to an empty host and would fail at connection time with an opaque error, so they are rejected here where the message can be actionable.

引数:

  • url (str) –

    URL string to validate.

戻り値:

  • bool

    True if URL is valid HTTP/HTTPS URL, False otherwise.

例:

>>> validate_url("https://example.com")
True
>>> validate_url("ftp://example.com")
False
>>> validate_url("https://")
False
>>> validate_url("https:///path")
False
ソースコード位置: httptap/utils.py
def validate_url(url: str) -> bool:
    """Validate URL format.

    Requires an http/https scheme and a non-empty host. A scheme alone is not
    enough: ``https:///path`` and ``https://?query`` parse to an empty host and
    would fail at connection time with an opaque error, so they are rejected
    here where the message can be actionable.

    Args:
        url: URL string to validate.

    Returns:
        True if URL is valid HTTP/HTTPS URL, False otherwise.

    Examples:
        >>> validate_url("https://example.com")
        True
        >>> validate_url("ftp://example.com")
        False
        >>> validate_url("https://")
        False
        >>> validate_url("https:///path")
        False

    """
    if _WHITESPACE_RE.search(url):
        return False

    try:
        parts = urlsplit(url)
    except ValueError:
        # Malformed authority, e.g. an unterminated IPv6 literal.
        return False

    return parts.scheme in {"http", "https"} and bool(parts.hostname)

sanitize_headers

sanitize_headers(
    headers: Mapping[str, str],
) -> dict[str, str]

Sanitize HTTP headers by masking sensitive values.

引数:

  • headers (Mapping[str, str]) –

    Dictionary of HTTP headers.

戻り値:

  • dict[str, str]

    New dictionary with sensitive values masked.

例:

>>> sanitize_headers({"Authorization": "Bearer secret"})
{'Authorization': 'Bear****cret'}
ソースコード位置: httptap/utils.py
def sanitize_headers(headers: Mapping[str, str]) -> dict[str, str]:
    """Sanitize HTTP headers by masking sensitive values.

    Args:
        headers: Dictionary of HTTP headers.

    Returns:
        New dictionary with sensitive values masked.

    Examples:
        >>> sanitize_headers({"Authorization": "Bearer secret"})
        {'Authorization': 'Bear****cret'}

    """
    sanitized = {}
    for key, value in headers.items():
        if key.lower() in SENSITIVE_HEADERS:
            sanitized[key] = mask_sensitive_value(value)
        else:
            sanitized[key] = value
    return sanitized

parse_http_date

parse_http_date(date_str: str) -> datetime | None

Parse HTTP date header to datetime.

Supports RFC 7231 HTTP-date format.

引数:

  • date_str (str) –

    Date string from HTTP Date header.

戻り値:

  • datetime | None

    Parsed datetime in UTC or None if parsing fails.

例:

>>> parse_http_date("Mon, 22 Oct 2025 12:00:00 GMT")
datetime.datetime(2025, 10, 22, 12, 0, tzinfo=UTC)
ソースコード位置: httptap/utils.py
def parse_http_date(date_str: str) -> datetime | None:
    """Parse HTTP date header to datetime.

    Supports RFC 7231 HTTP-date format.

    Args:
        date_str: Date string from HTTP Date header.

    Returns:
        Parsed datetime in UTC or None if parsing fails.

    Examples:
        >>> parse_http_date("Mon, 22 Oct 2025 12:00:00 GMT")
        datetime.datetime(2025, 10, 22, 12, 0, tzinfo=UTC)

    """
    try:
        # RFC 7231 format: "Mon, 22 Oct 2025 12:00:00 GMT"
        http_date = date_str.replace("GMT", "+0000")
        parsed = datetime.strptime(
            http_date,
            "%a, %d %b %Y %H:%M:%S %z",
        )
        return parsed.astimezone(UTC)
    except ValueError:
        return None

create_ssl_context

create_ssl_context(
    *, verify_ssl: bool, ca_bundle_path: str | None = None
) -> SSLContext

Return an SSL context honoring the requested verification policy.

引数:

  • verify_ssl (bool) –

    Whether to enforce certificate validation and modern security defaults.

  • ca_bundle_path (str | None, デフォルト: None ) –

    Path to custom CA certificate bundle file (PEM format). Only used when verify_ssl is True. If None, uses system CA bundle.

戻り値:

  • SSLContext

    Configured ssl.SSLContext instance.

ソースコード位置: httptap/utils.py
def create_ssl_context(*, verify_ssl: bool, ca_bundle_path: str | None = None) -> ssl.SSLContext:
    """Return an SSL context honoring the requested verification policy.

    Args:
        verify_ssl: Whether to enforce certificate validation and modern
            security defaults.
        ca_bundle_path: Path to custom CA certificate bundle file (PEM format).
            Only used when verify_ssl is True. If None, uses system CA bundle.

    Returns:
        Configured ``ssl.SSLContext`` instance.
    """
    if verify_ssl:
        context = ssl.create_default_context()

        if ca_bundle_path:
            try:
                context.load_verify_locations(cafile=ca_bundle_path)
            except (ssl.SSLError, FileNotFoundError, PermissionError, OSError) as e:
                msg = f"Failed to load CA bundle from '{ca_bundle_path}': {e}"
                raise ValueError(msg) from e

        return context

    # For legacy mode create a mutable context allowing older protocols.
    context = ssl.SSLContext(ssl.PROTOCOL_TLS)

    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE

    # Allow legacy cipher suites / key sizes (e.g., RC4, small DH groups)
    with suppress(ssl.SSLError):  # pragma: no cover - platform dependent
        context.set_ciphers("ALL:@SECLEVEL=0")

    # Permit older protocol versions to assist with legacy endpoints
    if hasattr(context, "minimum_version") and hasattr(ssl, "TLSVersion"):
        context.minimum_version = getattr(ssl.TLSVersion, "SSLv3", ssl.TLSVersion.MINIMUM_SUPPORTED)
    if hasattr(context, "maximum_version") and hasattr(ssl, "TLSVersion"):
        context.maximum_version = ssl.TLSVersion.MAXIMUM_SUPPORTED

    if hasattr(ssl, "OP_NO_SSLv3"):
        context.options &= ~ssl.OP_NO_SSLv3  # pragma: no cover - platform dependent
    if hasattr(ssl, "OP_NO_TLSv1"):
        context.options &= ~ssl.OP_NO_TLSv1
    if hasattr(ssl, "OP_NO_TLSv1_1"):
        context.options &= ~ssl.OP_NO_TLSv1_1

    return context

Constants

Selected values from httptap.constants (see the module source for the full set):

Timeouts and limits

from httptap.constants import (
    DEFAULT_TIMEOUT_SECONDS,  # 20.0 seconds
    TLS_PROBE_MAX_TIMEOUT_SECONDS,  # 5.0 seconds
    HTTP_DEFAULT_PORT,  # 80
    HTTPS_DEFAULT_PORT,  # 443
)

Exit codes

from httptap.constants import (
    EXIT_CODE_OK,  # 0  - Success (os.EX_OK)
    EXIT_CODE_USAGE,  # 64 - Invalid arguments (os.EX_USAGE)
    EXIT_CODE_SOFTWARE,  # 70 - Internal error (os.EX_SOFTWARE)
    EXIT_CODE_TEMPFAIL,  # 75 - Network/TLS error (os.EX_TEMPFAIL)
)

Example: complete usage

from httptap import HTTPTapAnalyzer

analyzer = HTTPTapAnalyzer(follow_redirects=True, timeout=30.0, http2=True)

steps = analyzer.analyze_url(
    "https://httpbin.io/bearer",
    headers={
        "Authorization": "Bearer token123",
        "Accept": "application/json",
        "User-Agent": "MyApp/1.0",
    },
)

for step in steps:
    print(f"Step {step.step_number}: {step.url}")
    print(f"  Status: {step.response.status}")
    print(f"  DNS: {step.timing.dns_ms:.2f}ms")
    print(f"  Connect: {step.timing.connect_ms:.2f}ms")
    print(f"  TLS: {step.timing.tls_ms:.2f}ms")
    print(f"  TTFB: {step.timing.ttfb_ms:.2f}ms")
    print(f"  Total: {step.timing.total_ms:.2f}ms")
    if step.network.ip:
        print(f"  IP: {step.network.ip} ({step.network.ip_family})")
    if step.network.cert_cn:
        print(f"  Certificate: {step.network.cert_cn} (expires in {step.network.cert_days_left} days)")

What's Next?