Content Handling¶
The glinda.content
module includes functions and classes that make
content negotiation in Tornado easier to extend. Tornado’s web framework
includes get_body_argument()
which handles
a handful of body encodings. It is difficult to add new content type handlers
in the current framework. Instead of adding all of the logic into the
RequestHandler
, glinda.content
maintains a mapping
from content type to encoder and decoder callables and exposes a mix-in that
implements content negotiation over a RequestHandler
.
The get_request_body()
method added by the
HandlerMixin
class will decode and cache the request body based on
a set of registered content types. You register encoder and decoder functions
associated with specific content types when your application starts up and
the HandlerMixin
will call them when it decodes the request body.
Request bodies are exposed from Tornado as raw byte strings. Calling
register_binary_type()
associates binary transcoding functions with a
specific MIME content type.
from glinda import content
import msgpack
content.register_binary_type('application/msgpack', msgpack.dumpb,
msgpack.loadb)
The transcoding functions are called to translate between dict
and
byte
representations when the Content-Type header matches
the specified value.
Many HTTP payloads are text-based and the protocol includes character set
negotiation separately from the content type. The character set of the
request body is usually indicated by the charset
content parameter ala
Content-Type: application/json; charset=utf-8
. You can register string-
based transcoding functions with register_text_type()
. Request body
processing will decode the byte string into a str
instance according
to the detected character set before calling text-based decoding functions.
If a character set is not included in the request headers, then an application
specified default value is used.
from glinda import content
import json
content.register_text_type('application/json', 'utf-8',
json.dumps, json.loads)
Binary registrations are preferred over text since they do not require the character transcoding process.
Once you have registered some content handlers, use the HandlerMixin
class to de-serialize requests and serialize responses. The following class
mimics the GET and POST functionality of the excellent http://httpbin.org
utility site.
class HttpbinHandler(content.HandlerMixin, web.RequestHandler):
"""Mimics http://httpbin.org/{get,post}"""
def get(self):
self.send_response(self.standard_response_dict)
self.finish()
def post(self):
response = self.standard_response_dict
response['data'] = repr(self.request.body)
response['files'] = {}
response['form'] = {}
response['body'] = self.get_request_body()
self.send_response(response)
self.finish()
@property
def standard_response_dict(self):
return {
'args': httpcompat.parse_qs(self.request.query),
'headers': dict(self.request.headers),
'origin': self.request.remote_ip,
'url': self.request.uri,
}
When you run examples/contentneg.py, it will run a Tornado application
listening on port 8000 with at least the JSON content handler enabled.
If the msgpack
module is available, then the application/x-msgpack
content type will be enabled. Likewise for the yaml
module and
application/yaml. Assuming that you have the PyYAML package installed,
then the following examples should work.
A request that explicitly requests a JSON response will get one.
GET / HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:8000
User-Agent: HTTPie/0.9.2
HTTP/1.1 200 OK
Content-Length: 204
Content-Type: application/json; charset=utf-8
Date: Sun, 09 Aug 2015 17:00:30 GMT
Etag: "7bccfbf9d3f99b4b9bc88ec4f27b1913e5c0b27e"
Server: TornadoServer/4.2
{
"args": {},
"headers": {
"Accept": "application/json",
"Accept-Encoding": "gzip, deflate",
"Connection": "keep-alive",
"Host": "localhost:8000",
"User-Agent": "HTTPie/0.9.2"
},
"origin": "::1",
"url": "/"
}
If you explicitly request application/yaml, then the same data will be encoded as a YAML document.
GET / HTTP/1.1
Accept: application/yaml
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:8000
User-Agent: HTTPie/0.9.2
HTTP/1.1 200 OK
Content-Length: 174
Content-Type: application/yaml; charset=utf-8
Date: Sun, 09 Aug 2015 17:04:23 GMT
Etag: "3d88b7fc99bb1b31807e88e4ea3d312d391c037b"
Server: TornadoServer/4.2
args: {}
headers: {Accept: application/yaml, Accept-Encoding: 'gzip, deflate',
Connection: keep-alive, Host: 'localhost:8000', User-Agent: HTTPie/0.9.2}
origin: ::1
url: /
The request handler simple needs to use HandlerMixin.get_request_body()
method to retrieve the request body and HandlerMixin.send_response()
to
transmit a response body.
Functions¶
-
glinda.content.
register_binary_type
(content_type, dumper, loader)[source]¶ Register handling for a binary content type.
- Parameters
content_type (str) – content type to register the hooks for
dumper – called to decode bytes into a dictionary. Calling convention:
dumper(obj_dict) -> bytes
.loader – called to encode a dictionary into a byte string. Calling convention:
loader(obj_bytes) -> dict
-
glinda.content.
register_text_type
(content_type, default_encoding, dumper, loader)[source]¶ Register handling for a text-based content type.
- Parameters
content_type (str) – content type to register the hooks for
default_encoding (str) – encoding to use if none is present in the request
dumper – called to decode a string into a dictionary. Calling convention:
dumper(obj_dict).encode(encoding) -> bytes
loader – called to encode a dictionary to a string. Calling convention:
loader(obj_bytes.decode(encoding)) -> dict
The decoding of a text content body takes into account decoding the binary request body into a string before calling the underlying dump/load routines.
Classes¶
-
class
glinda.content.
HandlerMixin
(*args, **kwargs)[source]¶ Mix this in over
RequestHandler
to enable content handling.-
get_request_body
()[source]¶ Decodes the request body and returns it.
- Returns
the decoded request body as a
dict
instance.- Raises
tornado.web.HTTPError
if the body cannot be decoded (415) or if decoding fails (400)
-
property
registered_content_types
¶ Yields the currently registered content types in some order.
-
send_response
(response_dict)[source]¶ Encode a response according to the request.
- Parameters
response_dict (dict) – the response to send
- Raises
tornado.web.HTTPError
if no acceptable content type exists
This method will encode response_dict using the most appropriate encoder based on the Accept request header and the available encoders. The result is written to the client by calling
self.write
after setting the response content type usingself.set_header
.
-