🐍 Python Course · Stage 3 · Lesson 58 of 89 · http.server — HTTP Server
Course Overview →

🔵 Read Beginner and Intermediate tabs. The Expert tab has the internals — worth reading once you finish Stage 3.

← socket — Networking Concept: Variables →
thecodex.expert · The Codex Family of Knowledge
Python Standard Library

http.server — Built-in HTTP Server

http.server provides a ready-made HTTP server for quick file sharing, local testing, and learning how the web works — no framework required.

import http.server docs.python.org Last verified:
Canonical Definition

The http.server module defines classes for implementing HTTP servers. It includes SimpleHTTPRequestHandler for serving files from a directory, and BaseHTTPRequestHandler for building custom request handlers. It is intended for development, testing, and learning — not production deployment.

🟩 Beginner

Turn any folder into a website

What you will learn: the one-command file server, and the same thing written in code with HTTPServer and SimpleHTTPRequestHandler.

How to read this tab: python -m http.server is the fastest way to share files or test a static site. Try it in a real terminal.

⏱ 25 min📄 2 sections🔶 Prerequisite: socket module
The idea

With a single command, http.server turns any folder into a website you can open in a browser. It is the fastest way to share files on a local network, test a static site, or see HTTP working firsthand — all built into Python, nothing to install.

--bind 0.0.0.0 exposes the server to your whole network. The default binds to localhost (only your machine). 0.0.0.0 makes it reachable by other devices — handy for sending a file to your phone, but anyone on the network can access those files while it runs.

The instant file server

The most common use needs no code at all — just a command. From any directory, run the module and it serves those files over HTTP.

Shellterminal
# Serve the current directory on port 8000
$ python -m http.server 8000

# Then open http://localhost:8000 in a browser.
# You'll see a clickable listing of the folder's files.

# Serve on a specific address (e.g. to share on your local network)
$ python -m http.server 8000 --bind 0.0.0.0

# Serve a different directory without changing into it
$ python -m http.server 8000 --directory /path/to/files

# Press Ctrl-C to stop the server.
--bind 0.0.0.0 exposes it to your network

By default the server binds to localhost (only your machine). --bind 0.0.0.0 makes it reachable by other devices on your network — handy for sending a file to your phone, but be aware anyone on the network can access those files while it runs.

The same thing, in code

Pythonfile_server.py
from http.server import HTTPServer, SimpleHTTPRequestHandler

# Serve the current directory programmatically
server = HTTPServer(("localhost", 8000), SimpleHTTPRequestHandler)
print("Serving on http://localhost:8000")
try:
    server.serve_forever()       # run until interrupted
except KeyboardInterrupt:
    server.shutdown()
    print("\nServer stopped")

# Serve a specific directory (Python 3.7+)
from functools import partial
handler = partial(SimpleHTTPRequestHandler, directory="/path/to/public")
server = HTTPServer(("localhost", 8000), handler)
# server.serve_forever()

✅ Beginner tab complete

  • I can serve a directory with python -m http.server
  • I know --bind 0.0.0.0 exposes it to my local network
  • I can run the same server in code with HTTPServer
  • I know it is for development and learning, not production

Continue to concurrent.futures →

🔵 Intermediate

Custom handlers and request handling

What you will learn: subclassing BaseHTTPRequestHandler with do_GET/do_POST, the fixed response order, and reading request bodies.

How to read this tab: The response order is fixed: send_response, send_header, end_headers, then write the body. Never reorder it.

⏱ 25 min📄 2 sections🔶 Prerequisite: Beginner tab

The response order is fixed and unforgiving. Always: send_response(code), then send_header(...) for each header, then end_headers(), then write the body to self.wfile. Skipping end_headers() or writing the body too early produces a broken response.

Building a custom request handler

To do more than serve files — return JSON, handle different URLs, accept form data — subclass BaseHTTPRequestHandler and define methods named after HTTP verbs: do_GET, do_POST, and so on.

Pythoncustom_handler.py
from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        # self.path is the requested URL path, e.g. "/api/status"
        if self.path == "/":
            self.send_response(200)              # status code
            self.send_header("Content-Type", "text/html")
            self.end_headers()                   # blank line ends headers
            self.wfile.write(b"<h1>Hello from Python</h1>")

        elif self.path == "/api/status":
            body = json.dumps({"status": "ok", "version": "1.0"}).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)

        else:
            self.send_error(404, "Not Found")    # built-in error response

    def do_POST(self):
        # Read the request body using the Content-Length header
        length = int(self.headers.get("Content-Length", 0))
        body = self.rfile.read(length)
        data = json.loads(body) if body else {}
        # ... process data ...
        reply = json.dumps({"received": data}).encode()
        self.send_response(201)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(reply)

server = HTTPServer(("localhost", 8000), MyHandler)
# server.serve_forever()
The response order is fixed

Always in this sequence: send_response(code), then send_header(...) for each header, then end_headers(), then write the body to self.wfile. Skipping end_headers() or writing the body too early produces a broken response.

http.server is for development and learning only. It is single-threaded by default, has no HTTPS, no protection against malicious requests, and limited performance. For production use Flask/Django + Gunicorn (WSGI) or FastAPI + Uvicorn (ASGI).

Why it is not for production

Pythonlimitations.py
# http.server is single-threaded by default — one request at a time.
# A slow request blocks all others. You can add threading:
from http.server import HTTPServer, SimpleHTTPRequestHandler
from socketserver import ThreadingMixIn

class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
    pass
# Python 3.7+ actually provides http.server.ThreadingHTTPServer directly:
from http.server import ThreadingHTTPServer   # handles requests in threads

# But even threaded, http.server lacks what production needs:
# - No HTTPS/TLS out of the box
# - No protection against malformed/malicious requests at scale
# - No process management, load balancing, or graceful restarts
# - Limited performance under real load

# For production, use a real WSGI/ASGI server with a framework:
# - Flask or Django + Gunicorn/uWSGI (WSGI)
# - FastAPI + Uvicorn (ASGI, async)
# http.server is for development, testing, and learning ONLY.
Commonly confused
http.server vs a web framework. http.server is a minimal, batteries-included server for local use. Flask, Django, and FastAPI are application frameworks with routing, templating, security, and an ecosystem. Use http.server to serve files or learn; use a framework to build an actual web application.
wfile and rfile are byte streams. Write your response body to self.wfile as bytes, and read the request body from self.rfile. They are file-like objects over the socket, so text must be encoded before writing.

✅ Intermediate tab complete

  • I can subclass BaseHTTPRequestHandler with do_GET and do_POST
  • I follow the order: send_response, send_header, end_headers, write body
  • I can read a request body using the Content-Length header
  • I know to write the body to self.wfile as bytes

Continue to concurrent.futures →

🔴 Expert

The handler lifecycle and WSGI

What you will learn: how the server dispatches to do_VERB methods, the request attributes, and the path to WSGI and production servers.

How to read this tab: Read to see how http.server connects to wsgiref, Gunicorn, and the production web stack.

⏱ 20 min📄 1 section🔶 Prerequisite: After Stage 4

The handler lifecycle, BaseHTTPRequestHandler internals, and WSGI

Each incoming connection causes the server to instantiate the handler class and call its handle_one_request method, which parses the request line and headers, then dispatches to the do_VERB method matching the HTTP method. The handler exposes the parsed request through attributes: self.command (the method), self.path (the raw URL path including query string), self.headers (an email.message.Message of the headers), and the self.rfile/self.wfile streams for body I/O.

Pythonhttp_advanced.py
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs

class Handler(BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.1"    # enable keep-alive (needs Content-Length)

    def do_GET(self):
        # Parse path and query string properly
        parsed = urlparse(self.path)
        path = parsed.path                       # "/search"
        params = parse_qs(parsed.query)          # {"q": ["python"]}

        # Inspect request headers
        user_agent = self.headers.get("User-Agent", "unknown")

        body = f"path={path} params={params}".encode()
        self.send_response(200)
        self.send_header("Content-Type", "text/plain")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, format, *args):
        # Override to customise or silence the default request logging
        pass     # silent server

# For real applications, the standard interface is WSGI (PEP 3333),
# which decouples the app from the server. The stdlib includes a
# reference WSGI server in wsgiref:
from wsgiref.simple_server import make_server

def app(environ, start_response):
    # environ holds the request; start_response sets status + headers
    start_response("200 OK", [("Content-Type", "text/plain")])
    return [b"Hello from a WSGI app"]

# with make_server("", 8000, app) as httpd:
#     httpd.serve_forever()
# This same 'app' runs unchanged under Gunicorn, uWSGI, etc. in production.

The relationship to production servers runs through WSGI (PEP 3333), the standard interface between Python web applications and servers. The stdlib's wsgiref.simple_server is a reference implementation useful for testing, and the key insight is that a WSGI app callable written against it runs unchanged under production servers like Gunicorn or uWSGI — only the server is swapped. For asynchronous applications, the analogous standard is ASGI, served by Uvicorn or Hypercorn. The progression mirrors the socket module's: http.server teaches the request/response mechanics directly, wsgiref introduces the application/server boundary, and production deployment swaps in a robust server while keeping the application code stable.

✅ Expert tab complete

  • I know the server dispatches to do_VERB based on the HTTP method
  • I can parse path and query string with urllib.parse
  • I know WSGI (PEP 3333) decouples the app from the server
  • I know a wsgiref app runs unchanged under Gunicorn/uWSGI in production

Continue to concurrent.futures →

Sources

1
Python Standard Library — http.server. docs.python.org/3/library/http.server.html.
2
Python Standard Library — BaseHTTPRequestHandler. docs.python.org/3/library/http.server.html.
3
Python Standard Library — wsgiref. docs.python.org/3/library/wsgiref.html.
4
PEP 3333 — Python Web Server Gateway Interface v1.0.1. peps.python.org/pep-3333/.
Source confidence: High Last verified: Primary source: docs.python.org http.server