4

Customizing Python's SimpleHTTPServer

 3 years ago
source link: https://parsiya.net/blog/2020-11-15-customizing-pythons-simplehttpserver/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Nov 15, 2020 - 3 minute read - Comments - python

Customizing Python's SimpleHTTPServer

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).

Code is at https://github.com/parsiya/Parsia-Code/tree/master/python-simplehttpserver.

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 It works!.

# 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"))
01.py
01.py

Custom Response Headers

Note the server adds some default headers. To modify these we can use send_header before calling end_headers. This is very useful for adding the Content-Type header.

# 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"))
02.py
02.py

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 Date and Server response headers cannot be changed :(.

Read Request Headers

I needed to read the incoming request headers. These are stored in the headers object. It is of type http.client.HTTPMessage which is a subclass of email.message.Message.

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 headers.get_all("header name").

# 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).

03.py
03.py

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:

  1. Read the Content-Length header in the incoming request.
  2. Read that many bytes from self.rfile.

    1. 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)
04.py
04.py

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()

Posted by Parsia Nov 15, 2020

The Same-Origin Policy Gone Wild


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK