import json
from tornado import web
from problemdetails import Problem
def _rfc_link(rfc_no, anchor=None):
if anchor:
return 'https://tools.ietf.org/html/rfc{0}#{1}'.format(rfc_no, anchor)
return 'https://tools.ietf.org/html/rfc{0}'.format(rfc_no)
type_link_map = {
100: _rfc_link(7231, 'section-6.2.1'),
101: _rfc_link(7231, 'section-6.2.2'),
200: _rfc_link(7231, 'section-6.3.1'),
201: _rfc_link(7231, 'section-6.3.2'),
202: _rfc_link(7231, 'section-6.3.3'),
203: _rfc_link(7231, 'section-6.3.4'),
204: _rfc_link(7231, 'section-6.3.5'),
205: _rfc_link(7231, 'section-6.3.6'),
206: _rfc_link(7233, 'section-4.1'),
207: _rfc_link(4918),
208: _rfc_link(5842),
226: _rfc_link(3229),
300: _rfc_link(7231, 'section-6.4.1'),
301: _rfc_link(7231, 'section-6.4.2'),
302: _rfc_link(7231, 'section-6.4.3'),
303: _rfc_link(7231, 'section-6.4.4'),
304: _rfc_link(7232, 'section-4.1'),
305: _rfc_link(7231, 'section-6.4.5'),
306: _rfc_link(7231, 'section-6.4.6'),
307: _rfc_link(7231, 'section-6.4.7'),
308: _rfc_link(7538),
400: _rfc_link(7231, 'section-6.5.1'),
401: _rfc_link(7235, 'section-3.1'),
402: _rfc_link(7231, 'section-6.5.2'),
403: _rfc_link(7231, 'section-6.5.3'),
404: _rfc_link(7231, 'section-6.5.4'),
405: _rfc_link(7231, 'section-6.5.5'),
406: _rfc_link(7231, 'section-6.5.6'),
407: _rfc_link(7235, 'section-3.2'),
408: _rfc_link(7231, 'section-6.5.7'),
409: _rfc_link(7231, 'section-6.5.8'),
410: _rfc_link(7231, 'section-6.5.9'),
411: _rfc_link(7231, 'section-6.5.10'),
412: _rfc_link(7232, 'section-4.2'),
413: _rfc_link(7231, 'section-6.5.11'),
414: _rfc_link(7231, 'section-6.5.12'),
415: _rfc_link(7231, 'section-6.5.13'),
416: _rfc_link(7233, 'section-4.4'),
417: _rfc_link(7231, 'section-6.5.14'),
418: _rfc_link(2324, 'section-2.3.2'),
421: _rfc_link(7540, 'section-9.1.2'),
422: _rfc_link(4918),
423: _rfc_link(4918),
424: _rfc_link(4918),
425: _rfc_link(8470),
426: _rfc_link(7231, 'section-6.5.15'),
428: _rfc_link(6585),
429: _rfc_link(6585),
431: _rfc_link(6585),
451: _rfc_link(7725),
500: _rfc_link(7231, 'section-6.6.1'),
501: _rfc_link(7231, 'section-6.6.2'),
502: _rfc_link(7231, 'section-6.6.3'),
503: _rfc_link(7231, 'section-6.6.4'),
504: _rfc_link(7231, 'section-6.6.5'),
505: _rfc_link(7231, 'section-6.6.6'),
506: _rfc_link(2295),
507: _rfc_link(4918),
508: _rfc_link(5842),
510: _rfc_link(2274),
511: _rfc_link(6585),
}
"""Mapping of HTTP status code to *type* link.
This table maps HTTP status codes to the IANA registered
specification for the code. You can add additional links or
replace ones that are here as you see fit. The error writer
uses this table to generate the default ``type`` link in
responses.
"""
[docs]class ErrorWriter(web.RequestHandler):
"""Render *application/problem+json* in ``write_error``
Include this class in the base class list to return errors as
`application/problem+json`_ documents instead of HTML.
.. attribute:: PROBLEM_DETAILS_MIME_TYPE
This class-level attribute controls the content type used by
`write_error`. Due to the way that `write_error` is called,
it is necessary to configure this. Setting the content type
header before calling :meth:`~tornado.web.RequestHandler.send_error`
DOES NOT work as expected.
If you need to change the response content type, change this
attribute at the class-level.
.. _application/problem+json: https://tools.ietf.org/html/rfc7807
"""
PROBLEM_DETAILS_MIME_TYPE = "application/problem+json"
json_encoder = json.JSONEncoder()
"""Used to encode problem response documents.
You set the attributes of this encoder to customize the creation
of problem documents.
"""
[docs] def write_error(self, status_code, **kwargs):
"""Render *application/problem+json* documents instead of HTML.
:param int status_code: HTTP status code that we returned
:keyword str detail: optional *detail* field to include in
the error document. This field is omitted by default.
:keyword str instance: optional *instance* to include in the
error document. This field is omitted by default.
:keyword str title: optional *title* to include in the error
document. THis field is omitted by default.
:keyword str type: optional *type* field to include in the
error document. This field defaults to a link to the
official HTTP specification of `status_code` if omitted
and `status_code` is a standard code.
See :rfc:`7807#section-3.1` for a description of each optional
field.
"""
body = {}
if 'exc_info' in kwargs:
exc_value = kwargs['exc_info'][1]
if isinstance(exc_value, Problem):
body = exc_value.document.copy()
if not body:
status_code = int(status_code)
body = {'status': status_code}
sentinel = object()
for kwarg in ('detail', 'instance', 'title', 'type'):
if kwargs.get(kwarg, sentinel) is not sentinel:
body[kwarg] = kwargs[kwarg]
if 'type' not in body:
try:
body['type'] = type_link_map[status_code]
except KeyError:
pass
self.set_header('Content-Type', self.PROBLEM_DETAILS_MIME_TYPE)
self.write(self.json_encoder.encode(body).encode('utf-8'))