http.server provides a simple HTTP server for serving files and building basic web services. Great for development and quick sharing.
Command-Line Usage
# Serve current directory on port 8000
python -m http.server
# Custom port
python -m http.server 3000
# Bind to specific address
python -m http.server --bind 127.0.0.1 8000
# Serve specific directory
python -m http.server --directory /path/to/files
# IPv6
python -m http.server --bind ::Basic Server in Code
from http.server import HTTPServer, SimpleHTTPRequestHandler
def run_server(port=8000):
server_address = ("", port)
httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
print(f"Serving on http://localhost:{port}")
httpd.serve_forever()
if __name__ == "__main__":
run_server()Custom Request Handler
from http.server import HTTPServer, BaseHTTPRequestHandler
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(b"<h1>Hello, World!</h1>")
def do_POST(self):
content_length = int(self.headers["Content-Length"])
body = self.rfile.read(content_length)
self.send_response(200)
self.send_header("Content-type", "application/json")
self.end_headers()
self.wfile.write(b'{"status": "received"}')
httpd = HTTPServer(("", 8000), MyHandler)
httpd.serve_forever()Routing
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
class APIHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/":
self.respond(200, "text/html", b"<h1>Home</h1>")
elif self.path == "/api/status":
self.respond_json({"status": "ok"})
elif self.path.startswith("/api/users/"):
user_id = self.path.split("/")[-1]
self.respond_json({"user_id": user_id})
else:
self.respond(404, "text/plain", b"Not Found")
def respond(self, status, content_type, body):
self.send_response(status)
self.send_header("Content-type", content_type)
self.end_headers()
self.wfile.write(body)
def respond_json(self, data):
body = json.dumps(data).encode()
self.respond(200, "application/json", body)Handling Query Parameters
from http.server import BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
class QueryHandler(BaseHTTPRequestHandler):
def do_GET(self):
parsed = urlparse(self.path)
query = parse_qs(parsed.query)
# /search?q=python&limit=10
# query = {"q": ["python"], "limit": ["10"]}
search_term = query.get("q", [""])[0]
limit = int(query.get("limit", ["10"])[0])
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write(f"Search: {search_term}, Limit: {limit}".encode())Handling POST Data
from http.server import BaseHTTPRequestHandler
import json
from urllib.parse import parse_qs
class PostHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_type = self.headers.get("Content-Type", "")
content_length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(content_length)
if "application/json" in content_type:
data = json.loads(body)
elif "application/x-www-form-urlencoded" in content_type:
data = parse_qs(body.decode())
else:
data = body
self.send_response(200)
self.send_header("Content-type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({"received": str(data)}).encode())Serving Files
from http.server import SimpleHTTPRequestHandler, HTTPServer
import os
class CustomFileHandler(SimpleHTTPRequestHandler):
def __init__(self, *args, directory=None, **kwargs):
self.directory = directory or os.getcwd()
super().__init__(*args, directory=self.directory, **kwargs)
def do_GET(self):
# Add custom headers
if self.path.endswith(".js"):
self.send_header("Content-Type", "application/javascript")
super().do_GET()
# Serve from specific directory
os.chdir("/path/to/files")
httpd = HTTPServer(("", 8000), SimpleHTTPRequestHandler)
httpd.serve_forever()CORS Headers
from http.server import SimpleHTTPRequestHandler, HTTPServer
class CORSHandler(SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
super().end_headers()
def do_OPTIONS(self):
self.send_response(200)
self.end_headers()
httpd = HTTPServer(("", 8000), CORSHandler)
httpd.serve_forever()Threaded Server
from http.server import HTTPServer, SimpleHTTPRequestHandler
from socketserver import ThreadingMixIn
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
"""Handle requests in separate threads."""
daemon_threads = True
httpd = ThreadedHTTPServer(("", 8000), SimpleHTTPRequestHandler)
httpd.serve_forever()HTTPS Server
from http.server import HTTPServer, SimpleHTTPRequestHandler
import ssl
httpd = HTTPServer(("", 443), SimpleHTTPRequestHandler)
# Wrap with SSL
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain("cert.pem", "key.pem")
httpd.socket = context.wrap_socket(httpd.socket, server_side=True)
httpd.serve_forever()Logging
from http.server import BaseHTTPRequestHandler
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class LoggingHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
logger.info("%s - %s", self.address_string(), format % args)
def do_GET(self):
logger.info(f"GET {self.path} from {self.client_address}")
self.send_response(200)
self.end_headers()
self.wfile.write(b"OK")Graceful Shutdown
from http.server import HTTPServer, SimpleHTTPRequestHandler
import signal
import sys
httpd = HTTPServer(("", 8000), SimpleHTTPRequestHandler)
def shutdown(signum, frame):
print("\nShutting down...")
httpd.shutdown()
sys.exit(0)
signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGTERM, shutdown)
print("Server running on http://localhost:8000")
httpd.serve_forever()Development Server Pattern
from http.server import HTTPServer, SimpleHTTPRequestHandler
import os
import sys
def serve(directory=".", port=8000, bind="127.0.0.1"):
"""Simple development server."""
os.chdir(directory)
handler = SimpleHTTPRequestHandler
httpd = HTTPServer((bind, port), handler)
print(f"Serving {os.getcwd()}")
print(f"http://{bind}:{port}")
print("Press Ctrl+C to stop")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nStopped")
httpd.shutdown()
if __name__ == "__main__":
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
serve(port=port)Common Patterns
from http.server import BaseHTTPRequestHandler
import json
class JSONAPIHandler(BaseHTTPRequestHandler):
"""Simple JSON API handler."""
def send_json(self, data, status=200):
body = json.dumps(data).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", len(body))
self.end_headers()
self.wfile.write(body)
def read_json(self):
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)
return json.loads(body) if body else {}
def do_GET(self):
self.send_json({"message": "Hello", "path": self.path})
def do_POST(self):
data = self.read_json()
self.send_json({"received": data})Limitations
# http.server is NOT for production:
# - Single-threaded by default
# - No security hardening
# - Basic features only
# - Performance is limited
# For production, use:
# - Flask/FastAPI for APIs
# - nginx for static files
# - gunicorn/uvicorn as WSGI/ASGI servershttp.server is perfect for quick file sharing and development. For anything production-facing, use a proper web framework.
React to this post: