Python's http.server module

Share
Copied to clipboard.
Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
3 min. read 2 min. video Python 3.8—3.12

Let's talk about Python's http.server module.

A directory trees of index.html files

We have a directory here that represents a static website:

~/comprehensions/_build/dirhtml $ ls index.html
index.html

We not only have an index.html file, but also a bunch of sub-directories, each with their own index.html file:

~/comprehensions/_build/dirhtml $ ls generator-expressions
index.html

The only way to really navigate this website locally is to serve up these files using some sort of HTTP server that is aware of these index files.

Python comes bundled with an HTTP server that we can use. It's called http.server.

Serving up HTML files with http.server

If we run this module from the command-line, Python will start up an HTTP server that serves up our current directory:

~/comprehensions/_build/dirhtml $ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

So when I visit this URL in my web browser, Python will send back all the files needed to serve up that page:

~/comprehensions/_build/dirhtml $ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
127.0.0.1 - - [15/Mar/2024 16:00:02] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [15/Mar/2024 16:00:02] "GET /_static/pygments.css HTTP/1.1" 200 -
127.0.0.1 - - [15/Mar/2024 16:00:02] "GET /_static/css/theme.css HTTP/1.1" 200 -
127.0.0.1 - - [15/Mar/2024 16:00:02] "GET /_static/documentation_options.js HTTP/1.1" 200 -
127.0.0.1 - - [15/Mar/2024 16:00:02] "GET /_static/jquery.js HTTP/1.1" 200 -
127.0.0.1 - - [15/Mar/2024 16:00:02] "GET /_static/underscore.js HTTP/1.1" 200 -
127.0.0.1 - - [15/Mar/2024 16:00:02] "GET /_static/_sphinx_javascript_frameworks_compat.js HTTP/1.1" 200 -
127.0.0.1 - - [15/Mar/2024 16:00:02] "GET /_static/js/theme.js HTTP/1.1" 200 -
127.0.0.1 - - [15/Mar/2024 16:00:02] "GET /_static/doctools.js HTTP/1.1" 200 -
127.0.0.1 - - [15/Mar/2024 16:00:02] "GET /_static/css/fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942 HTTP/1.1" 200 -
127.0.0.1 - - [15/Mar/2024 16:00:02] "GET /_static/css/fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe HTTP/1.1" 200 -
127.0.0.1 - - [15/Mar/2024 16:00:02] "GET /_static/css/fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e HTTP/1.1" 200 -
127.0.0.1 - - [15/Mar/2024 16:00:02] "GET /_static/css/fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2 HTTP/1.1" 200 -
127.0.0.1 - - [15/Mar/2024 16:00:03] code 404, message File not found
127.0.0.1 - - [15/Mar/2024 16:00:03] "GET /favicon.ico HTTP/1.1" 404 -

And if I click a link to go to one of these sub-directories, Python properly serves up the index.html file for that subdirectory:

127.0.0.1 - - [15/Mar/2024 16:00:09] "GET /list-comprehensions/ HTTP/1.1" 304 -

Customizing http.server with CLI arguments

Now what if you can't use the default port of 8000, or you don't want to?

You can change that port by sending an argument to http.server to specify the port that you'd like to serve that website from:

~/comprehensions/_build/dirhtml $ python -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

If you'd like to serve up a different directory besides the current one (which is the default) you can provide a -d argument specifying the directory to serve. We are making a Sphinx website here, and our static HTML files are under _build/dirhtml/, so we'll serve up that directory:

~/comprehensions $ python -m http.server -d _build/dirhtml/
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

Using http.server as a module

It's also possible to use the http.server module in code to make your own custom server:

#!/usr/bin/env python
import argparse
from http import server

parser = argparse.ArgumentParser(
    description="Start a local webserver with a Python terminal."
)
parser.add_argument(
    "--port", type=int, default=8000, help="port for the http server to listen on"
)
parser.add_argument(
    "--bind", type=str, default="127.0.0.1", help="Bind address (empty for all)"
)


class MyHTTPRequestHandler(server.SimpleHTTPRequestHandler):
    extensions_map = server.SimpleHTTPRequestHandler.extensions_map.copy()
    extensions_map.update(
        {
            ".wasm": "application/wasm",
        }
    )

    def end_headers(self) -> None:
        self.send_my_headers()
        super().end_headers()

    def send_my_headers(self) -> None:
        self.send_header("Cross-Origin-Opener-Policy", "same-origin")
        self.send_header("Cross-Origin-Embedder-Policy", "require-corp")


def main() -> None:
    args = parser.parse_args()
    if not args.bind:
        args.bind = None

    server.test(  # type: ignore[attr-defined]
        HandlerClass=MyHTTPRequestHandler,
        protocol="HTTP/1.1",
        port=args.port,
        bind=args.bind,
    )

if __name__ == "__main__":
    main()

With this server we're customizing the Content-Type headers that our server will send for certain file extensions.

I don't tend to find myself using http.server as a module within my code very often. The http.server module is more common to see used as a command-line script.

Use python -m http.server for a local HTTP server

The next time you find yourself with a bunch of HTML files that you wish you could serve up locally to test out a static website, use Python's http.server module.

Just run python -m http.server to serve up the current working directory.

A Python Tip Every Week

Need to fill-in gaps in your Python skills? I send weekly emails designed to do just that.