Python, create a Web (HTTP) server

By

Learn how to create an HTTP server in Python with the http.server module, from the quick python -m http.server command to a custom request handler class.

~~~

Python makes it super easy to create an HTTP server, via the http module of the standard library.

In particular, the http.server object is the thing we’re going to use.

Be careful: this module is for local work. The official docs say http.server is not recommended for production. It only implements basic security checks. Use it to preview files or try an idea on your machine.

Serve files from a folder

First, I want to mention one quick way to run an HTTP server from any folder, without writing any code:

python -m http.server 8000

This will run an HTTP server on port 8000, serving the files in the current folder. Of course it’s not an HTTP server fully featured like Nginx or Apache, but that’s often good enough for prototypes, or for your own test projects.

That command uses SimpleHTTPRequestHandler. It maps each URL to a file under the folder you started in. /notes.html serves notes.html. A folder with index.html or index.htm serves that file. Otherwise you get a directory listing.

Use SimpleHTTPRequestHandler when you want to share files. Use a custom BaseHTTPRequestHandler when you want to return your own HTML, JSON, or handle POST data.

You can also point it at a specific folder:

python -m http.server 8000 --directory /Users/flavio/notes

SimpleHTTPRequestHandler follows symbolic links, so a link inside that folder can expose a file outside it.

Older tutorials (including an earlier version of this one) used --cgi. CGI support (CGIHTTPRequestHandler and the --cgi flag) was deprecated in Python 3.13 and was removed in Python 3.15, so skip it. If you want to see how CGI works, I wrote How to handle web requests in C with CGI 1.1.

The same file server in code looks like this:

from http.server import SimpleHTTPRequestHandler, HTTPServer

with HTTPServer(('', 8000), SimpleHTTPRequestHandler) as server:
    server.serve_forever()

Who can connect

HTTPServer(('', 8000), handler) binds to every network interface. The empty string '' and 0.0.0.0 mean the same thing on IPv4. Anyone on the same Wi-Fi can open port 8000. That is also the default for python -m http.server.

If you only want your own machine, bind to 127.0.0.1:

python -m http.server 8000 --bind 127.0.0.1

In code, pass that address as the first item of the tuple:

with HTTPServer(('127.0.0.1', 8000), handler) as server:
    server.serve_forever()

My advice is to bind to 127.0.0.1 unless you have a reason to share the server with other machines.

A custom handler

Let’s now use that module in our code to make it programmatically serve an “Hello, World!” string to anyone connecting on port 8000:

from http.server import BaseHTTPRequestHandler, HTTPServer

class handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type','text/html')
        self.end_headers()

        message = "Hello, World!"
        self.wfile.write(bytes(message, "utf8"))

with HTTPServer(('', 8000), handler) as server:
    server.serve_forever()

After running this program locally you can try connecting using a Web browser to port http://localhost:8000.

This serves the same Hello, World! string to anyone visiting the site on http://localhost:8000, on GET requests to any URL, complete with a 200 response and a Content-type: text/html header.

We write to wfile, which contains the output stream for writing a response back to the client.

It works on GET requests because we implemented the handler do_GET method.

Route by path

self.path holds the URL path the client asked for. / and /about are different paths. The Hello World handler above ignored that and answered every GET the same way.

from http.server import BaseHTTPRequestHandler, HTTPServer

class handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/':
            message = "Home"
        elif self.path == '/about':
            message = "About"
        else:
            self.send_error(404, "Not found")
            return

        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write(bytes(message, "utf8"))

with HTTPServer(('127.0.0.1', 8000), handler) as server:
    server.serve_forever()

send_error() writes a complete error response, so we return after it.

Notice that self.path includes the query string if there is one. /about?x=1 is not equal to /about.

Handle POST

You can also implement do_HEAD(), do_POST() and any other HTTP method:

from http.server import BaseHTTPRequestHandler, HTTPServer

class handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type','text/html')
        self.end_headers()

        message = "Hello, World! Here is a GET response"
        self.wfile.write(bytes(message, "utf8"))
    def do_POST(self):
        self.send_response(200)
        self.send_header('Content-type','text/html')
        self.end_headers()

        message = "Hello, World! Here is a POST response"
        self.wfile.write(bytes(message, "utf8"))

with HTTPServer(('', 8000), handler) as server:
    server.serve_forever()

That POST handler replies, but it never reads the body. The body sits in rfile. You read it after looking at the Content-Length header. Read exactly that many bytes, or the next request on the same connection can get stuck.

from http.server import BaseHTTPRequestHandler, HTTPServer

class handler(BaseHTTPRequestHandler):
    def do_POST(self):
        length = int(self.headers.get('Content-Length', 0))
        body = self.rfile.read(length)
        message = "You posted: " + body.decode("utf8")

        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write(bytes(message, "utf8"))

with HTTPServer(('127.0.0.1', 8000), handler) as server:
    server.serve_forever()

Try it with curl:

curl -X POST -d 'hello' http://127.0.0.1:8000

Python has lots of different libraries we can use to handle applications built on top a Web server, including the very popular Flask and Django. If you want sockets without HTTP, I wrote a TCP server tutorial too.

Tagged: Python · All topics

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about python: