Handling POSTs

All of the previous examples 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. Disclaimer: 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 in a real application, make sure you understand the possible negative consequences.

As usual, we start with some imports. In addition to the Twisted imports, this example uses the cgi module to escape user-enteredcontent for inclusion in the output.

from twisted.web.server import Site
from twisted.web.resource import Resource
from twisted.internet import reactor, endpoints

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 (b"<!DOCTYPE html><html><head><meta charset='utf-8'>"
                b"<title></title></head><body>"
                b"<form><input name='the-field' type='text'></form>")

This is similar to the resource used in a previous installment . However, we’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):
        args = request.args[b"the-field"][0].decode("utf-8")
        escapedArgs = cgi.escape(args)
        return (b"<!DOCTYPE html><html><head><meta charset='utf-8'>"
                b"<title></title></head><body>"
                b"You submitted: " + escapedArgs.encode('utf-8'))

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 bytes objects (since there can be multiple inputs with the same name), which is why we 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(b"form", FormPage())
factory = Site(root)
endpoint = endpoints.TCP4ServerEndpoint(reactor, 8880)
endpoint.listen(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, endpoints

import cgi

class FormPage(Resource):
    def render_GET(self, request):
        return (b"<!DOCTYPE html><html><head><meta charset='utf-8'>"
                b"<title></title></head><body>"
                b"<form method='POST'><input name='the-field'></form>")

    def render_POST(self, request):
        args = request.args[b"the-field"][0].decode("utf-8")
        escapedArgs = cgi.escape(args)
        return (b"<!DOCTYPE html><html><head><meta charset='utf-8'>"
                b"<title></title></head><body>"
                b"You submitted: " + escapedArgs.encode('utf-8'))

root = Resource()
root.putChild(b"form", FormPage())
factory = Site(root)
endpoint = endpoints.TCP4ServerEndpoint(reactor, 8880)
endpoint.listen(factory)
reactor.run()