The other day I customized the Python built-in SimpleHTTPServer with some routes. I did not find a lot of info about it (most use it to serve files). This is how I did some basic customization.
This is for Python 3.8.6 (which what I have in my testing VM) but it should work on Python 3.9 (and probably the same for Python 2).
How to Serve Files
python -m http.server 8080 --bind 127.0.0.1.
Custom GET Responses
But I needed to customize the path. Let's start with a simple implementation. We need to create our own BaseHTTPRequestHandler.
from http.server import HTTPServer, BaseHTTPRequestHandler class MyHandler(BaseHTTPRequestHandler): def do_GET(self): pass httpd = HTTPServer(('localhost', 10000), MyHandler) httpd.serve_forever()
To respond to GET requests we need to add code to
do_GET. Let's say we want to
return a 200 response that says
# 01.py def do_GET(self): # send 200 response self.send_response(200) # send response headers self.end_headers() # send the body of the response self.wfile.write(bytes("01.py", "utf-8"))
Custom Response Headers
# 02.py def do_GET(self): # send 200 response self.send_response(200) # add our own custom header self.send_header("myheader", "myvalue") # send response headers self.end_headers() # send the body of the response self.wfile.write(bytes("It Works!", "utf-8"))
To override a header we cannot use
send_header because it will just add it as
a new header to the response. Based on the documentation it seems like the
Server response headers cannot be changed :(.
Read Request Headers
We can get the value of any header by name with
headers.get("header name"). To
get all values for a specific header (because headers can be repeated) use
# 03.py def do_GET(self): # get the value of the "Authorization" header and echo it. authz = self.headers.get("authorization") # send 200 response self.send_response(200) # send response headers self.end_headers() # send the body of the response self.wfile.write(bytes(authz, "utf-8"))
Note: Header names are not case-sensitive in HTTP (or here).
Reading The Body of POST Requests
To handle POST requests we need to implement
do_POST (d'oh). To read the body
of the POST request we:
- Read the
Content-Lengthheader in the incoming request.
Read that many bytes from
I could not find a way to read "all bytes" in
rfile. I had to rely on the header.
def do_POST(self): # read the content-length header content_length = int(self.headers.get("Content-Length")) # read that many bytes from the body of the request body = self.rfile.read(content_length) self.send_response(200) self.end_headers() # echo the body in the response self.wfile.write(body)
Server Over TLS
First, you need to create a private key and certificate in
pem format. To
create a self-signed certificate/key in one go:
openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem
Then modify the last lines of the script to:
httpd = HTTPServer(('localhost', 443), MyHandler) httpd.socket = ssl.wrap_socket(httpd.socket, server_side=True, certfile="certificate.pem", keyfile="key.pem") httpd.serve_forever()