A mock webserver to use for unit testing HTTP clients

With python -m SimpleHTTPServer it's easy to bring up an HTTP server to use to test HTTP client code, however it only supports GET requests, and I needed to test an HTTP client that needs to perform a file upload.

It took way more than I originally expected to put this together, so here it is, hopefully saving other people (including future me) some time:

#!/usr/bin/python3

import http.server
import cgi
import socketserver
import hashlib
import json

PORT = 8081

class Handler(http.server.SimpleHTTPRequestHandler):
    def do_POST(self):
        info = {
            "method": "POST",
            "headers": { k: v for k, v in self.headers.items() },
        }

        # From https://snipt.net/raw/f8ef141069c3e7ac7e0134c6b58c25bf/?nice
        form = cgi.FieldStorage(
            fp=self.rfile,
            headers=self.headers,
            environ={'REQUEST_METHOD':'POST',
                     'CONTENT_TYPE':self.headers['Content-Type'],
                     })

        postdata = {}
        for k in form.keys():
            if form[k].file:
                buf = form.getvalue(k)
                postdata[k] = {
                    "type": "file",
                    "name": form[k].filename,
                    "size": len(buf),
                    # json.dumps will not serialize a byte() object, so we
                    # return the shasum instead of the file body
                    "sha256": hashlib.sha256(buf).hexdigest(),
                }
            else:
                vals = form.getlist(k)
                if len(vals) == 1:
                    postdata[k] = {
                        "type": "field",
                        "val": vals[0],
                    }
                else:
                    postdata[k] = {
                        "type": "multifield",
                        "vals": vals,
                    }

        info["postdata"] = postdata

        resbody = json.dumps(info, indent=1)
        print(resbody)

        resbody = resbody.encode("utf-8")

        self.send_response(200)
        self.send_header("Content-type", "application/json")
        self.send_header("Content-Length", str(len(resbody)))
        self.end_headers()

        self.wfile.write(resbody)

class TCPServer(socketserver.TCPServer):
    # Allow to restart the mock server without needing to wait for the socket
    # to end TIME_WAIT: we only listen locally, and we may restart often in
    # some workflows
    allow_reuse_address = True

httpd = TCPServer(("", PORT), Handler)

print("serving at port", PORT)
httpd.serve_forever()