Add request body negotiation.

This commit is contained in:
Dave Shawley 2015-08-26 14:50:48 -04:00
parent 123793cd86
commit f766a24e26
2 changed files with 59 additions and 8 deletions

View file

@ -6,6 +6,7 @@ sprockets.mixins.media_type
import logging
from ietfparse import algorithms, errors, headers
from tornado import web
version_info = (0, 0, 0)
@ -74,6 +75,9 @@ class ContentSettings(object):
self._available_types.append(headers.parse_content_type(content_type))
self._handlers[content_type] = handler
def get(self, content_type, default=None):
return self._handlers.get(content_type, default)
@classmethod
def from_application(cls, application):
"""Retrieve the content settings from an application."""
@ -186,11 +190,28 @@ class ContentMixin(object):
return self._best_response_match
def get_request_body(self):
"""Fetch (and cache) the request body as a dictionary."""
"""
Fetch (and cache) the request body as a dictionary.
:raise web.HTTPError: if the content type cannot be decoded.
The status code is set to 415 Unsupported Media Type
"""
if self._request_body is None:
settings = ContentSettings.from_application(self.application)
handler = settings[settings.default_content_type]
self._request_body = handler.from_bytes(self.request.body)
content_type_header = headers.parse_content_type(
self.request.headers.get('Content-Type',
settings.default_content_type))
content_type = '/'.join([content_type_header.content_type,
content_type_header.content_subtype])
try:
handler = settings[content_type]
self._request_body = handler.from_bytes(self.request.body)
except KeyError:
raise web.HTTPError(415, 'cannot decode body of type %s',
content_type)
return self._request_body
def send_response(self, body, set_content_type=True):

View file

@ -1,16 +1,18 @@
import json
from tornado import testing
import msgpack
import examples
class ContentTypeTests(testing.AsyncHTTPTestCase):
class SendResponseTests(testing.AsyncHTTPTestCase):
def get_app(self):
return examples.make_application(debug=True)
def test_that_content_type_default_works(self):
response = self.fetch('/', method='POST',
body='{"attribute": "value"}',
response = self.fetch('/', method='POST', body='{}',
headers={'Content-Type': 'application/json'})
self.assertEqual(response.code, 200)
self.assertEqual(response.headers['Content-Type'],
@ -18,14 +20,42 @@ class ContentTypeTests(testing.AsyncHTTPTestCase):
def test_that_missing_content_type_uses_default(self):
response = self.fetch('/', method='POST', body='{}',
headers={'Accept': 'application/xml'})
headers={'Accept': 'application/xml',
'Content-Type': 'application/json'})
self.assertEqual(response.code, 200)
self.assertEqual(response.headers['Content-Type'],
'application/json; charset="utf-8"')
def test_that_accept_header_is_obeyed(self):
response = self.fetch('/', method='POST', body='{}',
headers={'Accept': 'application/msgpack'})
headers={'Accept': 'application/msgpack',
'Content-Type': 'application/json'})
self.assertEqual(response.code, 200)
self.assertEqual(response.headers['Content-Type'],
'application/msgpack')
class GetRequestBodyTests(testing.AsyncHTTPTestCase):
def get_app(self):
return examples.make_application(debug=True)
def test_that_request_with_unhandled_type_results_in_415(self):
response = self.fetch(
'/', method='POST', headers={'Content-Type': 'application/xml'},
body=(u'<request><name>value</name>'
u'<embedded><utf8>\u2731</utf8></embedded>'
u'</request>').encode('utf-8'))
self.assertEqual(response.code, 415)
def test_that_msgpack_request_returns_default_type(self):
body = {
'name': 'value',
'embedded': {
'utf8': u'\u2731'
}
}
response = self.fetch('/', method='POST', body=msgpack.packb(body),
headers={'Content-Type': 'application/msgpack'})
self.assertEqual(response.code, 200)
self.assertEqual(json.loads(response.body.decode('utf-8')), body)