From 1fe22df71944ff8763df930d807e634002355c45 Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Thu, 14 Oct 2021 07:06:07 -0400 Subject: [PATCH] Gracefully handle transcoder encoding failures. Since the form encoder refuses to handle nested sequences, the content mixin explicitly handles this case instead of letting the unhandled exception bubble up. --- docs/history.rst | 2 ++ sprockets/mixins/mediatype/content.py | 17 +++++++++++----- tests.py | 29 +++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/docs/history.rst b/docs/history.rst index f02c664..4dea9bc 100644 --- a/docs/history.rst +++ b/docs/history.rst @@ -9,6 +9,8 @@ Version History and there is no default content type configured - Deprecate not having a default content type configured - Fail gracefully when a transcoder does not exist for the default content type +- Fail gracefully when a transcoder raises a :exc:`TypeError` or :exc:`ValueError` when encoding + the response .. _application/x-www-formurlencoded: https://url.spec.whatwg.org/#application/x-www-form-urlencoded diff --git a/sprockets/mixins/mediatype/content.py b/sprockets/mixins/mediatype/content.py index 2ba01a1..2a27d3f 100644 --- a/sprockets/mixins/mediatype/content.py +++ b/sprockets/mixins/mediatype/content.py @@ -423,8 +423,15 @@ class ContentMixin(web.RequestHandler): settings.default_content_type) raise web.HTTPError(500) else: - content_type, data_bytes = handler.to_bytes(body) - if set_content_type: - self.set_header('Content-Type', content_type) - self.add_header('Vary', 'Accept') - self.write(data_bytes) + try: + content_type, data_bytes = handler.to_bytes(body) + except (TypeError, ValueError) as e: + self._logger.error( + 'selected transcoder (%s) failed to encode response ' + 'body: %s', handler.__class__.__name__, e) + raise web.HTTPError(500, reason='Response Encoding Failure') + else: + if set_content_type: + self.set_header('Content-Type', content_type) + self.add_header('Vary', 'Accept') + self.write(data_bytes) diff --git a/tests.py b/tests.py index 9f5a28a..330afd6 100644 --- a/tests.py +++ b/tests.py @@ -177,6 +177,35 @@ class SendResponseTests(testing.AsyncHTTPTestCase): self.assertEqual('application/foo+json', response.headers.get('Content-Type')) + def test_that_transcoder_failures_result_in_500(self): + class FailingTranscoder: + content_type = 'application/vnd.com.example.bad' + + def __init__(self): + self.exc_class = TypeError + + def to_bytes(self, inst_data, encoding=None): + raise self.exc_class('I always fail at this') + + def from_bytes(self, data_bytes, encoding=None): + return {} + + transcoder = FailingTranscoder() + content.add_transcoder(self.application, transcoder) + for _ in range(2): + response = self.fetch( + '/', + method='POST', + body=b'{}', + headers={ + 'Accept': 'application/vnd.com.example.bad', + 'Content-Type': 'application/json', + }, + ) + self.assertEqual(500, response.code) + self.assertEqual('Response Encoding Failure', response.reason) + transcoder.exc_class = ValueError + class GetRequestBodyTests(testing.AsyncHTTPTestCase): def setUp(self):