Welcome to the seventh installment of “Twisted Web in 60 seconds” in which I’ll show you how to handle POST
requests. All of the previous installments have focused on GET
requests. Unlike GET
requests, POST
requests can have a request body - extra data after the request headers; for example, data representing the contents of an HTML form. Twisted Web makes this data available to applications via the Request object.
Here’s an example web server which renders a static HTML form and then generates a dynamic page when that form is posted back to it. (While it’s convenient for this example, it’s often not a good idea to make a resource that POST
s to itself; this isn’t about Twisted Web, but the nature of HTTP in general; if you do this, make sure you understand the possible negative consequences).
As usual, we start with some imports (see previous installments for details). In addition to the Twisted imports, this example uses the cgi
module to escape user-entered content for inclusion in the output.
from twisted.web.server import Site
from twisted.web.resource import Resource
from twisted.internet import reactor
import cgi
Next, we’ll define a resource which is going to do two things. First, it will respond to GET
requests with a static HTML form:
class FormPage(Resource):
def render_GET(self, request):
return '<html><body><form method="POST"><input name="the-field" type="text" /></form></body></html>'
This is similar to the static resource I used as an example in a previous installment. However, I’ll now add one more method to give it a second behavior; this render_POST
method will allow it to accept POST
requests:
def render_POST(self, request):
return '<html><body>You submitted: %s</body></html>' % (cgi.escape(request.args["the-field"][0]),)
The main thing to note here is the use of request.args
. This is a dictionary-like object that provides access to the contents of the form. The keys in this dictionary are the names of inputs in the form. Each value is a list containing strings (since there can be multiple inputs with the same name), which is why I had to extract the first element to pass to cgi.escape
. request.args
will be populated from form contents whenever a POST
request is made with a content type of application/x-www-form-urlencoded or multipart/form-data (it’s also populated by query arguments for any type of request).
Finally, the example just needs the usual site creation and port setup:
root = Resource()
root.putChild("form", FormPage())
factory = Site(root)
reactor.listenTCP(8880, factory)
reactor.run()
Run the server and visit http://localhost:8880/form
, submit the form, and watch it generate a page including the value you entered into the single field.
Here’s the complete source for the example:
from twisted.web.server import Site
from twisted.web.resource import Resource
from twisted.internet import reactor
import cgi
class FormPage(Resource):
def render_GET(self, request):
return '<html><body><form method="POST"><input name="the-field" type="text" /></form></body></html>'
def render_POST(self, request):
return '<html><body>You submitted: %s</body></html>' % (cgi.escape(request.args["the-field"][0]),)
root = Resource()
root.putChild("form", FormPage())
factory = Site(root)
reactor.listenTCP(8880, factory)
reactor.run()
Since I’m getting a little bored with some of the boilerplate involved in these examples, the next installment will introduce rpy
files, a good way to try out new concepts and APIs (like the ones presented in this series) without all the repetitive boilerplate.