Return 406 Not Acceptable appropriately.

If there is no default content type and nothing matches in the Accept
header, previous versions would fail with an Internal Server Error
because of an unhandled exception.  Instead we should be returning
a "406 Not Acceptable".
This commit is contained in:
Dave Shawley 2021-10-02 10:36:19 -04:00
parent 25ba46e960
commit 4dd46eda5b
No known key found for this signature in database
GPG key ID: F41A8A99298F8EED
3 changed files with 32 additions and 3 deletions

View file

@ -4,6 +4,8 @@ Version History
:compare:`Next <3.0.4...master>`
--------------------------------
- Add type annotations (see :ref:`type-info`)
- Return a "406 Not Acceptable" if the :http:header:`Accept` header values cannot be matched
and there is no default content type configured
:compare:`3.0.4 <3.0.3...3.0.4>` (2 Nov 2020)
---------------------------------------------

View file

@ -391,8 +391,14 @@ class ContentMixin(web.RequestHandler):
"""
settings = get_settings(self.application, force_instance=True)
# TODO -- account for get_response_type returning None
handler = settings[self.get_response_content_type()] # type: ignore
response_type = self.get_response_content_type()
if response_type is None:
self._logger.error('failed to find a suitable response '
'content type for request')
self._logger.error('please set a default content type')
raise web.HTTPError(406)
handler = settings[response_type]
content_type, data_bytes = handler.to_bytes(body)
if set_content_type:
self.set_header('Content-Type', content_type)

View file

@ -61,8 +61,13 @@ def pack_bytes(payload):
class SendResponseTests(testing.AsyncHTTPTestCase):
def setUp(self):
self.application = None
super().setUp()
def get_app(self):
return examples.make_application()
self.application = examples.make_application()
return self.application
def test_that_content_type_default_works(self):
response = self.fetch('/',
@ -129,6 +134,22 @@ class SendResponseTests(testing.AsyncHTTPTestCase):
self.assertEqual(response.code, 200)
self.assertEqual(response.headers['Content-Type'], 'expected/content')
def test_that_no_default_content_type_will_406(self):
# NB if the Accept header is omitted, then a default of `*/*` will
# be used which results in a match against any registered handler.
# Using an accept header forces the "no match" case.
settings = content.get_settings(self.application, force_instance=True)
settings.default_content_type = None
settings.default_encoding = None
response = self.fetch('/',
method='POST',
body='{}',
headers={
'Accept': 'application/xml',
'Content-Type': 'application/json',
})
self.assertEqual(response.code, 406)
class GetRequestBodyTests(testing.AsyncHTTPTestCase):
def setUp(self):