diff --git a/sprockets/mixins/media_type.py b/sprockets/mixins/media_type.py index 1305377..6965b17 100644 --- a/sprockets/mixins/media_type.py +++ b/sprockets/mixins/media_type.py @@ -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): diff --git a/tests.py b/tests.py index 390fa1b..e815601 100644 --- a/tests.py +++ b/tests.py @@ -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'value' + u'\u2731' + u'').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)