Funciones avanzadas¶
Esta guía cubre patrons de uso avanzado y opciones de personalización para httptap.
Resolución DNS personalizada¶
Puedes proporcionar implementations personalizadas de resolutor DNS usando la API de Python. httptap siempre marca la dirección IP resuelta (IPv4/IPv6) conservando el nombre de host original para la cabecera Host y el SNI de TLS. Los literales IPv6 se ponen entre corchetes automáticamente, de modo que los resolutores personalizados solo necesitan devolver la tupla IP/familia correcta.
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")
Como esta subclase solo sobrescribe resolve(), httptap la llama en lugar del resolve_all() heredado, así que cada host se resuelve a una única dirección y no hay respaldo a la siguiente dirección cuando falla una conexión. Sobrescribe también resolve_all() para conservar ese respaldo; consulta Respaldo de direcciones con resolve_all(). Lanza DNSResolutionError desde un resolutor personalizado cuando un nombre no se pueda resolver, para que el fallo se informe como un error de red y no como uno interno.
Inspección TLS personalizada¶
httptap lee la versión de TLS, el cifrado y los detalles del certificado directamente de la conexión activa que sirvió la respuesta, por lo que un TLSInspector personalizado no reemplaza esos datos. Solo actúa como respaldo: httptap lo llama en una solicitud HTTPS cuando la conexión activa no expone datos TLS y no se usa ningún proxy, y entonces su resultado rellena los campos TLS. El inspector recibe el nombre de host, el puerto y el tiempo de espera restante, y abre su propia conexión. Lanza httptap.TLSInspectionError cuando la inspección falle para que el paso se siga informando sin detalles TLS. La sonda de diagnóstico que recopila los detalles del certificado tras un fallo de verificación siempre usa el SocketTLSInspector integrado, nunca un inspector personalizado.
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())
Uso programático¶
Usa httptap como biblioteca de Python para integral en tus aplicaciones.
Análisis básico¶
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")
Con cabeceras personalizadas¶
from httptap import HTTPTapAnalyzer
analyzer = HTTPTapAnalyzer()
headers = {"Authorization": "Bearer token123", "Accept": "application/json"}
steps = analyzer.analyze_url("https://httpbin.io/bearer", headers=headers)
Seguir redirecciones¶
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)}")
Cada salto de redirección sigue las mismas reglas de cabeceras y métodos que la CLI: las cabeceras de credenciales no se envían a otro origen, y 303 (o 301/302 tras POST) cambia a GET sin cuerpo. Consulta --follow para más detalles.
Enviar el cuerpo de la solicitud¶
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"},
)
Ignorar la verificación TLS¶
Al resolver problemas en entornos de staging o en hosts con certificados autofirmados, puedes omitir la validación TLS:
La solicitud sigue registrando los metadatos de TLS, pero los errores de certificado se suprimen para que puedas centrarte en el flujo del protocolo. Usa este flag solo en entornos de confianza, porque desactiva la protección frente a ataques de intermediario (man-in-the-middle). El cliente relaja muchos requisitos de cifrado y protocolo (hashes débiles, versiones de TLS más antiguas, grupos DH pequeños) para que sea más probable que los endpoints heredados completen la negociación TLS. Los algoritmos extremadamente obsoletos que OpenSSL elimina por completo (por ejemplo, RC4, 3DES en algunas plataformas) pueden fallar aun en este modo.
Uso de proxies¶
Dirige las solicitudes a través de un proxy de salida (HTTP, HTTPS, SOCKS5/SOCKS5H):
Ignore todas las variables de entorno de proxy y conecta directamente:
La salida Rich y la exportación JSON incluyen la URI del proxy y su origen (por ejemplo, (from arg --proxy), (from env HTTPS_PROXY), (bypassed by env no_proxy)) para que puedas confirmar qué ruta se usó.
Protocolos de proxy y resolución DNS¶
httptap admite cuatro protocolos de proxy, cada uno con un comportamiento de resolución DNS distinto:
| Protocolo | DNS resuelto por | Caso de uso |
|---|---|---|
socks5h:// | Servidor proxy | Privacidad, redes corporativas, acceso a DNS interno |
http:// | Servidor proxy | Proxies HTTP estándar (método CONNECT) |
https:// | Servidor proxy | Conexión cifrada al proxy |
socks5:// | Cliente (local) | Cuando necesitas controlar la resolución DNS |
El sufijo h en socks5h significa "hostname" (una convención de curl). Con socks5h://, el nombre de host se envía al proxy, que lo resuelve. Con socks5://, el cliente resuelve el DNS localmente y envía la IP al proxy.
Con socks5://, cuando el host se resuelve en varias direcciones y el proxy informa de que no puede conectarse a una de ellas, httptap prueba la siguiente dirección. Los fallos al alcanzar el propio proxy o al autenticarse en él no se reintentan, ya que se repetirían para cada dirección. Los destinos con un literal IPv6 (https://[2001:db8::1]/) funcionan con todos los tipos de proxy.
Proxies por variables de entorno¶
Cuando no se proporciona el flag --proxy, httptap comprueba las variables de entorno:
no_proxy/NO_PROXY- Lista separada por comas de hosts a omitir (la minúscula tiene prioridad)https_proxy/HTTPS_PROXY- Proxy para solicitudes HTTPS (la minúscula tiene prioridad)http_proxy/HTTP_PROXY- Proxy para solicitudes HTTP (la minúscula tiene prioridad)all_proxy/ALL_PROXY- Proxy alternativo para todos los protocolos
El flag --proxy siempre tiene prioridad sobre las variables de entorno.
Una variable sin esquema (proxy.internal:3128) se trata como un proxy http://, igual que en curl. Si la variable seleccionada no contiene una URL de proxy válida (un esquema no admitido, falta el host, un puerto no válido), la solicitud falla con un error de red (código de salida 75) que nombra la variable y muestra la URL con la contraseña enmascarada.
Patrones de NO_PROXY:
*- Omite el proxy para todos los hostsexample.com- El propio host y todos sus subdominios.example.com- Solo los subdominios de example.com, no example.com en sísub.example.com- sub.example.com y sus subdominios
La coincidencia no distingue mayúsculas de minúsculas. No se admiten rangos CIDR ni entradas con puerto.
Paquetes de CA personalizados¶
Para endpoints internos firmados por una CA privada, proporciona un paquete PEM con --cacert:
La salida de la CLI mostrará TLS CA: custom bundle para indicar que se usó el almacén de confianza ajeno al sistema. Las exportaciones JSON incluyen network.tls_custom_ca: true para que las herramientas posteriores puedan detectar la confianza personalizada. El flag es mutuamente excluyente con --ignore-ssl.
Ejecutores de solicitud personalizados¶
Para un comportamiento totalmente personalizado puedes proporcionar tu propio ejecutor de solicitudes. Los ejecutores reciben todos los parámetros empaquetados dentro de RequestOptions, de modo que los nuevos flags añadidos por httptap siguen siendo retrocompatibles. Reenvía todos los campos que admita tu transporte; omitir uno cambia la solicitud sin avisar (por ejemplo, ignorar method y content convierte un POST en un GET sin cuerpo, e ignorar proxy o ca_bundle_path descarta la configuración de --proxy o --cacert).
from httptap import HTTPTapAnalyzer, RequestExecutor, RequestOptions, RequestOutcome
from httptap.http_client import make_request
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)
timing, network, response = make_request(
options.url,
options.timeout,
deadline=options.deadline,
method=options.method,
content=options.content,
http2=options.http2,
verify_ssl=options.verify_ssl,
ca_bundle_path=options.ca_bundle_path,
proxy=options.proxy,
noproxy=options.noproxy,
dns_resolver=options.dns_resolver,
tls_inspector=options.tls_inspector,
timing_collector=options.timing_collector,
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'}
force_new_connection está obsoleto y se ignora, por lo que no se reenvía. Para envolver el comportamiento predeterminado en lugar de reimplementarlo, delega en HTTPClientRequestExecutor().execute(options). Lanza httptap.http_client.HTTPClientError ante fallos de transporte para que el analizador los registre como errores de red (código de salida 75) con datos parciales; cualquier otra excepción se informa como un error interno (código de salida 70).
Visualización personalizada¶
Crea tu propia visualización implementando el protocolo Visualizer.
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)
Formatos de exportación personalizados¶
Implementa formatos de exportación personalizados más allá de 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")
Monitorización del rendimiento¶
Usa httptap para la monitorización continua del rendimiento.
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)
Análisis por lotes¶
Analiza varias URL de forma concurrente.
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")
Manejo de errores¶
Maneja los errores con elegancia al analizar URL. has_error solo se activa cuando la propia solicitud falla (DNS, conexión, TLS o tiempo de espera); una respuesta HTTP 4xx/5xx es un paso completado, así que comprueba response.status por separado.
from httptap import HTTPTapAnalyzer
analyzer = HTTPTapAnalyzer()
steps = analyzer.analyze_url("https://nonexistent.invalid")
step = steps[0]
if step.has_error:
print(f"Error: {step.error}")
elif step.response.status >= 400:
print(f"HTTP error: {step.response.status}")
else:
print(f"Status: {step.response.status}")
Integración con frameworks de pruebas¶
Usa httptap en tus suites de pruebas para verificar los requisitos de rendimiento.
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"
Configuración específica por entorno¶
Configura httptap de forma diferente para distintos entornos.
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")
Consejos de depuración¶
Habilitar el registro detallado¶
import logging
# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
from httptap import HTTPTapAnalyzer
analyzer = HTTPTapAnalyzer()
steps = analyzer.analyze_url("https://httpbin.io")
Inspeccionar el tráfico HTTP en bruto¶
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}")
¿Qué sigue?¶
-
Documentación detallada de la interfaz
-
Amplía httptap y contribuye
-
Cómo funcionan las publicaciones