In an ongoing project to implement a web application server that is as simple as possible we now implement handling POST requests
Handling POST requests
The HTTP POST method is often the preferred way to transfer information from a form to the server. If we use POST the arguments don't end up in the webserver log for example and it allows us to use a file upload tag.
Depending on the encoding
attribute of a form
element, POST data might be encode as a number of key/value pairs (one on each line) or a multipart MIME message (useful if you want to upload files). More information can be found here.
Wielding the power of Python's cgi
module
Decoding a multipart MIME message isn't trivial but lucky for us we can offload the hard work to an existing module: cgi
. It is part of the standard Python distribution and although designed to implemented cgi scripts we can co-opt its functionality for our purpose. All we have to do is add a do_POST()
method to our ApplicationRequestHandler
class as shown below:
from cgi import FieldStorage from os import environ class ApplicationRequestHandler(BaseHTTPRequestHandler): def do_POST(self): ob=self._find_app() environ['REQUEST_METHOD'] = 'POST' fs=FieldStorage(self.rfile,headers=self.headers) kwargs={} for name in fs: for i in fs.getlist(name): if fs[name].file: kwargs[name] = fs[name].file kwargs[name].srcfilename = fs[name].filename else: kwargs[name] = fs[name] return self._execute(ob,kwargs)
We factored out some common code that is also used in the do_GET()
method (in the _find_app()
and _execute()
methods, not shown here), but basically we instantiate a cgi.FieldStorage
instance and pass it the input filestream along with a dictionary of the HTTP headers we have enounterd so far. Because the cgi
module expects some parameters to be present in the environment we set the REQUEST_METHOD
to POST
because that information not part of the headers. The FieldStorage
instance returned can be used as dictionary with the names of the parameters as keys.
Any file input parameters are a bit special. The contents of the uploaded file are stored in a temporary file and if we would access this parameter the value would be the contents of this file as a byte object. We'd rather pass on the temporary file object to prevent passing around the contents of really big files unnecessarily. We can check if a parameter is an uploaded file by checking its file
parameter (line 14). If the argument is a file, we tack on the original file name (that is, the one the user's PC) because we might want to use that later. The final line passes the arguments we have extracted to the _execute()
method. This method is factored out from the do_GET()
method. It locates the function to execute and checks its arguments against any annotations