Webapps on App Engine part 2: Request & Response handling
This is part of a series on writing a webapp framework for App Engine Python. For details, see the introductory post here.
This post is mostly background on request and response encoding/decoding. If you're already fairly familiar with how this works in CGI and in higher level frameworks, you may want to skip this and wait for the next posting, on request handlers.
If you've ever written a CGI script, you'll be well aware of how much of a pain interpreting and decoding CGI environment variables and headers can be - so much so that the first action of many is to find or write a library to handle it for you! And if you've ever coded in a CGI-inspired language such as PHP, you're probably familiar with how much of a pain managing the combination of response headers and output content can likewise be.
As a result of this, one of the most basic tools a framework offers is some form of abstraction for request and response data. Typically, this takes care of collecting and parsing HTTP headers, parsing the query string (if any), decoding POSTed form data, and so forth. Better frameworks also take care of common header-related tasks, like interpreting cookies and headers that should affect the nature of the response.
Very nearly every framework defines its own request and response abstraction. Django and Werkzeug both define their own, for example, while the authors of the Paste framework recognized this trend towards repetition, and created WebOb, a standalone library that incorporates just the request and response objects from the Paste framework.
Due to the constraints, different frameworks' takes on request and response objects tend to be very similar - there's often not much to choose between them. For the same reason, it's also the case that writing our own request handling would be tedious, and not particularly enlightening. With that in mind, we'll go ahead and use the WebOb library, for these reasons:
- It fulfills all our requirements admirably
- It's available as a separate library
- It's already bundled with the App Engine SDK!
- It has several advanced features we can make use of to improve our framework overall
Without further ado, let's take a look at the basic workings of WebOb. Unsurprisingly, there are two major classes we need to concern ourselves with: Request, and Response. Let's examine them in order.
The Request object is fairly straightforward: To construct one, you must pass in a WSGI environment dictionary. The resulting Request object then exposes everything you would generally expect to have available: Information about the URL and the request method, headers, query and form data, and so forth. It also provides access to the raw body of the request (if any), and interprets cookies for you. Finally, it breaks common headers out into their own properties, including the 'accept' headers and those dealing with conditional requests and caching.
The response object is a little more surprising. In addition to all the usual functionality, such as allowing you to set headers and fill out the response body, the Response object is itself a fully-formed WSGI application. Apart from providing an easy way to actually send the response back to the client, this also provides additional functionality: The Webob response object automatically takes care of handling HTTP conditional requests for you - instantly providing your app with a bunch of features that many interactive apps don't support without much manual involvement, or at all.
Let's see a practical example of how to use the WebOb Request and Response objects in a WSGI application:
def application(environ, start_response): request = webob.Request(environ) response = webob.Response(request=request, conditional_response=True) response.content_type = 'text/plain' out = response.body_file out.write("Hello, %s!" % (request.GET.get('name', 'world'),)) return response(environ, start_response)
As you can see, this is starting to look a little saner, and more like what you're used to if you regularly use frameworks. There's still a bit of boilerplate here, but we'll be taking care of that when we discuss request and response handlers in the next post.
One final feature of the WebOb library that we'll be making use of is its set of status code exceptions. This comprises a comprehensive set of classes for all the HTTP response codes. These classes are WSGI applications, so they can be used as a WSGI application, and they are also valid Python exception classes, so they can be thrown. This is particularly useful because it allows you to 'throw' an error status code in an intuitive fashion - for example:
def some_handler(request): if not request.is_authorized: raise exc.HTTPForbidden() # Otherwise, do stuff...
You're not limited to using 400 and 500 status codes in this fashion. Somewhat less intuitive, but often useful, is the ability to throw a redirect:
def some_handler(request): if not request.logged_in: raise exc.HTTPFound(location='/login') # Otherwise, user is logged in...
We'll be making use of these when we start to deal with request handling, in the next post.
Update: Added a few paragraphs about WebOb's status code exceptions.
I'm away at the Lovely Snow Sprint all next week. Posts in the series will continue as normal, but may be displaced by updates from the sprint if anything particularly fascinating happens. ;)Previous Post Next Post