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