When you need fine-grained control over HTTP requests, Python's http.client module gives you direct access to the protocol layer.
Basic GET Request
import http.client
conn = http.client.HTTPSConnection("api.example.com")
conn.request("GET", "/users/1")
response = conn.getresponse()
print(response.status, response.reason) # 200 OK
data = response.read()
conn.close()POST with JSON Body
import http.client
import json
conn = http.client.HTTPSConnection("api.example.com")
payload = json.dumps({"name": "Owen", "role": "engineer"})
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer token123"
}
conn.request("POST", "/users", body=payload, headers=headers)
response = conn.getresponse()
result = json.loads(response.read())
conn.close()Connection Reuse
Keep connections open for multiple requests:
import http.client
conn = http.client.HTTPSConnection("api.example.com")
# Multiple requests on same connection
for user_id in [1, 2, 3]:
conn.request("GET", f"/users/{user_id}")
response = conn.getresponse()
data = response.read() # Must read before next request
print(f"User {user_id}: {response.status}")
conn.close()Critical: Always read the response body before making another request on the same connection.
Handling Timeouts
import http.client
import socket
conn = http.client.HTTPSConnection("api.example.com", timeout=5)
try:
conn.request("GET", "/slow-endpoint")
response = conn.getresponse()
except socket.timeout:
print("Request timed out")
finally:
conn.close()Context Manager Pattern
import http.client
from contextlib import contextmanager
@contextmanager
def http_connection(host, port=443, timeout=10):
conn = http.client.HTTPSConnection(host, port, timeout=timeout)
try:
yield conn
finally:
conn.close()
# Usage
with http_connection("api.example.com") as conn:
conn.request("GET", "/health")
response = conn.getresponse()
print(response.read().decode())Reading Response Headers
import http.client
conn = http.client.HTTPSConnection("example.com")
conn.request("GET", "/")
response = conn.getresponse()
# All headers as list of tuples
print(response.getheaders())
# Specific header
content_type = response.getheader("Content-Type")
cache_control = response.getheader("Cache-Control", "no-cache")
conn.close()Chunked Transfer Encoding
Handle streaming responses:
import http.client
conn = http.client.HTTPSConnection("api.example.com")
conn.request("GET", "/stream")
response = conn.getresponse()
# Check if chunked
if response.getheader("Transfer-Encoding") == "chunked":
while True:
chunk = response.read(1024)
if not chunk:
break
process_chunk(chunk)
else:
data = response.read()
conn.close()Custom HTTP Methods
import http.client
conn = http.client.HTTPSConnection("api.example.com")
# PATCH request
conn.request("PATCH", "/users/1",
body='{"status": "active"}',
headers={"Content-Type": "application/json"})
# DELETE request
conn.request("DELETE", "/users/1")
# OPTIONS request
conn.request("OPTIONS", "/users")
response = conn.getresponse()
allowed = response.getheader("Allow") # "GET, POST, OPTIONS"Debugging with set_debuglevel
import http.client
conn = http.client.HTTPSConnection("api.example.com")
conn.set_debuglevel(1) # Print request/response details
conn.request("GET", "/users/1")
response = conn.getresponse()
# Prints: send/reply headers, etc.HTTP vs HTTPS
import http.client
# HTTP (port 80, no encryption)
http_conn = http.client.HTTPConnection("example.com")
# HTTPS (port 443, TLS encryption)
https_conn = http.client.HTTPSConnection("example.com")
# Custom port
custom_conn = http.client.HTTPSConnection("api.example.com", port=8443)When to Use http.client
Use http.client when you need:
- No dependencies: Part of standard library
- Protocol-level control: Custom headers, methods, connection management
- Connection pooling: Manual connection reuse
- Debugging: Low-level visibility into HTTP traffic
For most use cases, requests or httpx are more convenient. But http.client is invaluable when you need to go lower or avoid external dependencies.
Real-World Example: API Client
import http.client
import json
from typing import Any
class APIClient:
def __init__(self, host: str, token: str):
self.host = host
self.token = token
self.conn = None
def connect(self):
self.conn = http.client.HTTPSConnection(self.host)
def close(self):
if self.conn:
self.conn.close()
def request(self, method: str, path: str,
body: dict = None) -> dict:
headers = {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json"
}
payload = json.dumps(body) if body else None
self.conn.request(method, path, body=payload, headers=headers)
response = self.conn.getresponse()
data = response.read()
if response.status >= 400:
raise Exception(f"API error: {response.status}")
return json.loads(data) if data else {}
# Usage
client = APIClient("api.example.com", "secret-token")
client.connect()
try:
user = client.request("GET", "/users/1")
client.request("PATCH", "/users/1", {"name": "Updated"})
finally:
client.close()The http.client module is Python's gateway to HTTP at the protocol level—essential knowledge for building robust network code.
React to this post: