Python’s http.server module provides HTTP server classes for serving static files and building custom request handlers. from http.server import HTTPServer, BaseHTTPRequestHandler, SimpleHTTPRequestHandler, ThreadingHTTPServer. Create: HTTPServer((host, port), HandlerClass) — single-threaded; ThreadingHTTPServer((host, port), HandlerClass) — thread-per-request. Handler lifecycle: override do_GET, do_POST, do_PUT, do_DELETE, etc. in a BaseHTTPRequestHandler subclass — each method is called for the matching HTTP verb. Request data: self.command → method string; self.path → URL path + query; self.headers → http.client.HTTPMessage (dict-like); self.rfile.read(content_length) → POST body bytes. Response: self.send_response(200), self.send_header("Content-Type", "application/json"), self.end_headers(), self.wfile.write(body_bytes). Static serving: SimpleHTTPRequestHandler serves files from the current directory with directory listings; pass directory= kwarg to serve from a different root. CLI: python -m http.server 8080 — serves current directory. TLS: wrap with ssl.wrap_socket or use ssl.SSLContext. Claude Code generates development servers, JSON REST handlers, file upload endpoints, and test HTTP fixtures.
CLAUDE.md for http.server
## http.server Stack
- Stdlib: from http.server import HTTPServer, BaseHTTPRequestHandler
- ThreadingHTTPServer, SimpleHTTPRequestHandler
- Handler: class MyHandler(BaseHTTPRequestHandler):
- def do_GET(self):
- self.send_response(200)
- self.send_header("Content-Type", "application/json")
- self.end_headers()
- self.wfile.write(b'{"ok":true}')
- Serve: with HTTPServer(("", 8080), MyHandler) as s: s.serve_forever()
- Static: python -m http.server 8080 --directory /path/to/files
http.server Custom Handler Pipeline
# app/httputil.py — JSON handler, routing, static+API, upload, test fixture
from __future__ import annotations
import io
import json
import mimetypes
import os
import socket
import socketserver
import threading
import urllib.parse
from http.server import (BaseHTTPRequestHandler, HTTPServer,
SimpleHTTPRequestHandler, ThreadingHTTPServer)
from pathlib import Path
# ─────────────────────────────────────────────────────────────────────────────
# 1. JSON REST handler
# ─────────────────────────────────────────────────────────────────────────────
class JsonHTTPHandler(BaseHTTPRequestHandler):
"""
Base handler for JSON REST APIs.
Override handle_get(path, params) → dict and handle_post(path, body) → dict.
"""
log_requests: bool = False # class-level config
def log_message(self, format: str, *args) -> None:
if self.log_requests:
super().log_message(format, *args)
def _read_json_body(self) -> object:
length = int(self.headers.get("Content-Length", 0))
raw = self.rfile.read(length)
if not raw:
return None
try:
return json.loads(raw.decode("utf-8"))
except json.JSONDecodeError:
return None
def _send_json(self, data: object, status: int = 200) -> None:
body = json.dumps(data, ensure_ascii=False).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def _send_error_json(self, status: int, message: str) -> None:
self._send_json({"error": message}, status=status)
def _parse_path(self) -> tuple[str, dict[str, list[str]]]:
parsed = urllib.parse.urlparse(self.path)
params = urllib.parse.parse_qs(parsed.query)
return parsed.path, params
def do_GET(self) -> None:
path, params = self._parse_path()
try:
result = self.handle_get(path, params)
self._send_json(result)
except NotImplementedError:
self._send_error_json(404, f"GET {path} not handled")
except Exception as e:
self._send_error_json(500, str(e))
def do_POST(self) -> None:
path, _ = self._parse_path()
body = self._read_json_body()
try:
result = self.handle_post(path, body)
self._send_json(result, status=201)
except NotImplementedError:
self._send_error_json(404, f"POST {path} not handled")
except Exception as e:
self._send_error_json(500, str(e))
def handle_get(self, path: str, params: dict) -> object:
raise NotImplementedError
def handle_post(self, path: str, body: object) -> object:
raise NotImplementedError
# ─────────────────────────────────────────────────────────────────────────────
# 2. Router-based handler
# ─────────────────────────────────────────────────────────────────────────────
Route = tuple[str, str] # (method, path)
class RouterHandler(JsonHTTPHandler):
"""JSON handler with a simple dict-based route table."""
routes: dict[Route, "callable"] = {}
@classmethod
def route(cls, method: str, path: str):
"""Decorator: @RouterHandler.route("GET", "/hello")"""
def decorator(fn):
cls.routes[(method.upper(), path)] = fn
return fn
return decorator
def handle_get(self, path: str, params: dict) -> object:
fn = self.routes.get(("GET", path))
if fn is None:
raise NotImplementedError
return fn(self, path, params)
def handle_post(self, path: str, body: object) -> object:
fn = self.routes.get(("POST", path))
if fn is None:
raise NotImplementedError
return fn(self, path, body)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Static + API hybrid handler
# ─────────────────────────────────────────────────────────────────────────────
def make_hybrid_handler(static_dir: str | Path, api_prefix: str = "/api"):
"""
Create a handler class that serves static files for non-API paths
and dispatches JSON requests for paths starting with api_prefix.
Example:
HandlerClass = make_hybrid_handler("/var/www/public", api_prefix="/api")
server = HTTPServer(("", 8080), HandlerClass)
"""
static_root = str(static_dir)
prefix = api_prefix
class HybridHandler(SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=static_root, **kwargs)
def log_message(self, fmt, *args):
pass
def do_GET(self):
if self.path.startswith(prefix):
self._handle_api()
else:
super().do_GET()
def _handle_api(self):
path = self.path[len(prefix):]
data = {"path": path, "method": "GET", "api": True}
body = json.dumps(data).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
return HybridHandler
# ─────────────────────────────────────────────────────────────────────────────
# 4. Test fixture: ephemeral server
# ─────────────────────────────────────────────────────────────────────────────
class EphemeralServer:
"""
Context manager for an ephemeral HTTP server on a random free port.
Useful for test fixtures.
Example:
handler_cls.routes = {} # reset
@handler_cls.route("GET", "/ping")
def ping(self, path, params): return {"pong": True}
with EphemeralServer(handler_cls) as srv:
import urllib.request
resp = urllib.request.urlopen(f"http://127.0.0.1:{srv.port}/ping")
print(json.loads(resp.read()))
"""
def __init__(self, handler_class, threaded: bool = True):
self.handler_class = handler_class
self.threaded = threaded
self.server: HTTPServer | None = None
self.port: int | None = None
self._thread: threading.Thread | None = None
def __enter__(self) -> "EphemeralServer":
with socket.socket() as s:
s.bind(("127.0.0.1", 0))
self.port = s.getsockname()[1]
cls = ThreadingHTTPServer if self.threaded else HTTPServer
self.server = cls(("127.0.0.1", self.port), self.handler_class)
self.server.allow_reuse_address = True
self._thread = threading.Thread(
target=self.server.serve_forever, daemon=True
)
self._thread.start()
return self
def __exit__(self, *_):
if self.server:
self.server.shutdown()
self.server.server_close()
@property
def base_url(self) -> str:
return f"http://127.0.0.1:{self.port}"
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile
import urllib.request
print("=== http.server demo ===")
# ── Set up RouterHandler routes ───────────────────────────────────────────
RouterHandler.routes = {}
@RouterHandler.route("GET", "/")
def index(self, path, params):
return {"message": "hello from http.server", "path": path}
@RouterHandler.route("GET", "/echo")
def echo(self, path, params):
name = params.get("name", ["world"])[0]
return {"echo": name}
@RouterHandler.route("POST", "/add")
def add(self, path, body):
a = body.get("a", 0) if body else 0
b = body.get("b", 0) if body else 0
return {"result": a + b}
# ── EphemeralServer test ──────────────────────────────────────────────────
with EphemeralServer(RouterHandler) as srv:
print(f"\n Server on {srv.base_url}")
# GET /
resp = urllib.request.urlopen(f"{srv.base_url}/")
data = json.loads(resp.read())
print(f"\n--- GET / ---")
print(f" {data}")
# GET /echo?name=cmath
resp = urllib.request.urlopen(f"{srv.base_url}/echo?name=stdlib")
data = json.loads(resp.read())
print(f"\n--- GET /echo?name=stdlib ---")
print(f" {data}")
# POST /add
body = json.dumps({"a": 40, "b": 2}).encode()
req = urllib.request.Request(
f"{srv.base_url}/add",
data=body,
headers={"Content-Type": "application/json"},
method="POST",
)
resp = urllib.request.urlopen(req)
data = json.loads(resp.read())
print(f"\n--- POST /add {{a:40, b:2}} ---")
print(f" {data}")
# 404
try:
urllib.request.urlopen(f"{srv.base_url}/missing")
except urllib.error.HTTPError as e:
err = json.loads(e.read())
print(f"\n--- GET /missing ---")
print(f" status={e.code} {err}")
# ── Hybrid static+API server (demo only, not blocking) ────────────────────
print("\n--- make_hybrid_handler ---")
with tempfile.TemporaryDirectory() as tmp:
(Path(tmp) / "index.html").write_text(
"<html><body>Hello</body></html>"
)
HandlerClass = make_hybrid_handler(tmp)
print(f" Created hybrid handler for static dir {tmp}")
print(f" API prefix: /api")
print("\n=== done ===")
For the Flask / FastAPI (PyPI) alternatives — Flask’s @app.route("/path") and FastAPI’s @app.get("/path") provide automatic request parsing, response serialization, middleware, CORS, authentication, OpenAPI docs, and production WSGI/ASGI compatibility — use Flask or FastAPI for any production web service; use http.server for local development servers, zero-dependency test fixtures in unit tests, ad hoc file serving with python -m http.server, and embedded control planes in tools that cannot add third-party packages. For the waitress / gunicorn (PyPI) alternatives — production-grade WSGI servers that serve Flask/FastAPI apps with worker pools, graceful restarts, and request queuing — never use http.server.HTTPServer or ThreadingHTTPServer in production; they have no backpressure, no graceful shutdown, and no path sanitization hardening — development and testing use only. The Claude Skills 360 bundle includes http.server skill sets covering JsonHTTPHandler with _send_json()/_read_json_body()/do_GET()/do_POST(), RouterHandler with @route decorator dispatch, make_hybrid_handler() static+API factory, and EphemeralServer context manager for test fixtures. Start with the free tier to try HTTP server patterns and http.server pipeline code generation.