watch this The wheels are turning, slowly turning. home
Twisted Web in 60 seconds: handling POSTs 2009-10-02


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