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