http.client en Python: HTTP de bajo nivel

Publicado el: 20/08/2026
Tempo de leitura: 4 minutos
Rack de servidores que representa conexiones HTTP de bajo nivel con http.client en Python

El módulo http.client implementa el lado cliente de HTTP y HTTPS en un nivel inferior a urllib.request. Expone conexiones, solicitudes y respuestas directamente, permitiendo controlar rutas, cabeceras, cuerpos, streaming, reutilización, túneles CONNECT y contextos TLS.

Este control es útil para aprender HTTP, probar servidores, construir clientes específicos y diagnosticar integraciones. Para APIs normales, una biblioteca superior suele ser más productiva. Esta guía cubre el ciclo correcto de HTTPSConnection, lecturas limitadas, JSON, archivos, conexiones persistentes, proxies, errores y seguridad.

Cuándo usar http.client

Úsalo cuando necesites comportamiento HTTP/1.1 explícito, handlers propios o cero dependencias. Para URLs completas, redirects, cookies y autenticación, consulta urllib.request en Python. Las aplicaciones grandes se benefician de clientes con pooling y timeouts más completos.

Primera conexión HTTPS

import http.client

connection = http.client.HTTPSConnection(
    "www.python.org",
    timeout=10,
)

try:
    connection.request(
        "GET",
        "/",
        headers={
            "Host": "www.python.org",
            "Accept": "text/html",
            "User-Agent": "MiHerramienta/1.0",
        },
    )
    response = connection.getresponse()
    print(response.status, response.reason)
    body = response.read(200_000)
finally:
    connection.close()

El constructor recibe hostname y puerto, no una URL completa. El target de la solicitud suele ser una ruta absoluta como /docs?page=1. Configura siempre timeout.

HTTPS y SSLContext

HTTPSConnection valida certificado y hostname por defecto. Para una CA privada, proporciona un contexto seguro.

import ssl

context = ssl.create_default_context(cafile="empresa-ca.pem")
connection = http.client.HTTPSConnection(
    "api-interna.example",
    timeout=10,
    context=context,
)

No uses un contexto no verificado para evitar fallos. Corrige la cadena. La guía de ssl en Python explica TLS seguro.

Ciclo request y getresponse

Envía la solicitud, obtiene la respuesta y lee o cierra por completo esa respuesta antes de enviar otra en la misma conexión.

connection.request("GET", "/primero")
first = connection.getresponse()
first_data = first.read()

connection.request("GET", "/segundo")
second = connection.getresponse()
second_data = second.read()

Los bytes pendientes impiden encuadrar correctamente la siguiente respuesta. Los cuerpos grandes deben consumirse en bloques hasta EOF.

Lecturas limitadas

MAX_BYTES = 5 * 1024 * 1024

def read_limited(response, maximum=MAX_BYTES) -> bytes:
    chunks = []
    total = 0

    while chunk := response.read(64 * 1024):
        total += len(chunk)
        if total > maximum:
            response.close()
            raise ValueError("La respuesta superó el límite")
        chunks.append(chunk)

    return b"".join(chunks)

Content-Length ayuda, pero cuenta los bytes reales. Para descargas, escribe en un archivo temporal.

Cabeceras

response = connection.getresponse()
content_type = response.getheader("Content-Type")
content_length = response.getheader("Content-Length")
all_headers = response.getheaders()

getheader() une valores repetidos con comas. Esto no es correcto para todos los campos, especialmente Set-Cookie. Usa getheaders() cuando necesites conservar ocurrencias.

Enviar JSON

import json

payload = json.dumps({"name": "Ejemplo"}).encode("utf-8")
headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
    "Content-Length": str(len(payload)),
}

connection.request("POST", "/items", body=payload, headers=headers)
response = connection.getresponse()
raw = read_limited(response, 500_000)

Para un cuerpo bytes, la biblioteca puede calcular Content-Length. Si lo declaras, debe coincidir exactamente.

Subir archivos

Cuando el cuerpo es un archivo o iterable sin tamaño, la biblioteca usa normalmente transferencia chunked.

with open("archivo.bin", "rb") as file:
    connection.request(
        "PUT",
        "/upload",
        body=file,
        headers={"Content-Type": "application/octet-stream"},
    )
    response = connection.getresponse()
    response.read()

Algunos servidores antiguos no aceptan chunked. Proporciona una longitud correcta cuando sea necesario. Un archivo parcialmente leído no puede repetirse sin reposicionarlo.

Streaming de descarga

from pathlib import Path

connection.request("GET", "/download")
response = connection.getresponse()

if response.status != 200:
    response.read(20_000)
    raise RuntimeError(f"HTTP {response.status}")

with Path("download.tmp").open("wb") as output:
    total = 0
    while chunk := response.read(64 * 1024):
        total += len(chunk)
        if total > 100 * 1024 * 1024:
            response.close()
            raise ValueError("Archivo demasiado grande")
        output.write(chunk)

Verifica SHA-256 antes de mover el archivo. Consulta hashlib en Python.

Estados HTTP

http.client no lanza automáticamente una excepción por 404 o 500. Inspecciona response.status.

if 200 <= response.status < 300:
    data = read_limited(response)
elif response.status == 404:
    response.read(20_000)
    raise LookupError("Recurso no encontrado")
else:
    response.read(20_000)
    raise RuntimeError(f"El servidor devolvió {response.status}")

Redirects, 429, Retry-After y autenticación también quedan bajo responsabilidad de la aplicación.

Conexiones persistentes

HTTP/1.1 permite reutilización, reduciendo handshakes TCP y TLS. Reutiliza solo después de consumir la respuesta. No compartas una conexión simultáneamente entre hilos sin coordinación estricta.

Si ocurre RemoteDisconnected antes de una operación idempotente, cierra y reconecta. No repitas POST ciegamente porque el servidor puede haberlo procesado.

Solicitudes HEAD

connection.request("HEAD", "/archivo.zip")
response = connection.getresponse()
print(response.status, response.getheader("Content-Length"))
response.read()

HEAD no tiene cuerpo, pero completa el ciclo. Los metadatos no sustituyen los límites reales de un GET posterior.

Túnel CONNECT

proxy = http.client.HTTPSConnection("proxy.example", 8443, timeout=10)
proxy.set_tunnel(
    "www.python.org",
    443,
    headers={"Host": "www.python.org:443"},
)
proxy.request("GET", "/")
response = proxy.getresponse()

Protege credenciales del proxy y valida el certificado del destino. Desde Python 3.12, CONNECT usa HTTP/1.1 y get_proxy_response_headers() permite inspeccionar las cabeceras del proxy.

API paso a paso

putrequest(), putheader(), endheaders() y send() exponen etapas inferiores. Úsalas solo si request() no basta; es fácil crear headers o chunked inválidos.

Excepciones y estado incierto

try:
    connection.request("GET", "/")
    response = connection.getresponse()
    data = read_limited(response)
except (TimeoutError, OSError, http.client.HTTPException) as error:
    connection.close()
    raise RuntimeError("Fallo HTTP") from error

Excepciones importantes: IncompleteRead, BadStatusLine, LineTooLong, ResponseNotReady y RemoteDisconnected. Cierra la conexión cuando el framing sea incierto.

Debug

set_debuglevel(1) imprime detalles en stdout. Úsalo solo en desarrollo controlado, porque puede mostrar Authorization y otros datos sensibles.

Protección SSRF

Si host y puerto proceden del usuario, valida DNS e IP antes de conectar. Bloquea loopback, redes privadas, link-local y metadata de nube. Las conexiones directas también sufren SSRF y DNS rebinding.

Cuándo elegir una API superior

Usa urllib.request o un cliente externo cuando necesites redirects, cookies, parsing, autenticación y pools. http.client es apropiado para control fino y aprendizaje.

La guía para integrar APIs con Python muestra un flujo completo.

Errores comunes

Los fallos habituales son omitir timeout, pasar una URL completa como ruta, no consumir respuesta, compartir conexión sin seguridad, leer sin límite, desactivar TLS, repetir POST tras fallo y activar debug con credenciales.

Buenas prácticas

Usa HTTPS validado, timeout, cuerpos limitados, streaming y cierre garantizado. Consume o cierra cada respuesta. Reutiliza solo conexiones sanas, distingue métodos idempotentes y restringe hosts externos cuando sea posible.

Conclusión

http.client ofrece acceso directo al cliente HTTP/1.1 de Python. Controla conexiones, requests, respuestas, streaming y proxies, pero deja redirects, estados, retries y límites a tu código. Úsalo cuando ese control justifique la responsabilidad adicional.

Consulta la documentación oficial de http.client y el RFC 9112 sobre HTTP/1.1.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Desarrollador configurando un servidor HTTPS y certificado TLS con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crea un servidor HTTPS local en Python

    Aprende HTTPSServer en Python para crear servicios HTTPS locales, configurar certificados, usar hilos y comprender sus límites.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Archivos protegidos que representan extracción segura de TAR con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extrae TAR con seguridad

    Aprende tarfile extraction_filter en Python para extraer archivos TAR con validación, seguridad y control de rutas.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python mostrando avisos controlados con catch_warnings
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: captura warnings en pruebas Python

    Aprende catch_warnings en Python para capturar, probar y controlar avisos con filtros específicos y alcance seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python que representa referencias persistentes de pickle
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serializa referencias externas

    Aprende pickle persistent_id en Python para referencias externas estables, validación, seguridad, rendimiento y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binario que representa buffers y vistas de memoria en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: cuenta valores sin copiar buffers

    Aprende memoryview.count en Python para contar bytes y valores en buffers sin copias, con formatos, límites y buenas prácticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python y anotaciones de tipos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evita imports circulares en anotaciones

    Aprende annotationlib en Python para inspeccionar anotaciones diferidas, evitar imports circulares y crear herramientas seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026