413c92c631
Original sanic exception handler only could handle exact matching exceptions. New `lookup` method will provide ability to looking up their ancestors without additional cost of performance.
222 lines
5.6 KiB
Python
222 lines
5.6 KiB
Python
from .response import text, html
|
|
from .log import log
|
|
from traceback import format_exc, extract_tb
|
|
import sys
|
|
|
|
TRACEBACK_STYLE = '''
|
|
<style>
|
|
body {
|
|
padding: 20px;
|
|
font-family: Arial, sans-serif;
|
|
}
|
|
|
|
p {
|
|
margin: 0;
|
|
}
|
|
|
|
.summary {
|
|
padding: 10px;
|
|
}
|
|
|
|
h1 {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
h3 {
|
|
margin-top: 10px;
|
|
}
|
|
|
|
h3 code {
|
|
font-size: 24px;
|
|
}
|
|
|
|
.frame-line > * {
|
|
padding: 5px 10px;
|
|
}
|
|
|
|
.frame-line {
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.frame-code {
|
|
font-size: 16px;
|
|
padding-left: 30px;
|
|
}
|
|
|
|
.tb-wrapper {
|
|
border: 1px solid #f3f3f3;
|
|
}
|
|
|
|
.tb-header {
|
|
background-color: #f3f3f3;
|
|
padding: 5px 10px;
|
|
}
|
|
|
|
.frame-descriptor {
|
|
background-color: #e2eafb;
|
|
}
|
|
|
|
.frame-descriptor {
|
|
font-size: 14px;
|
|
}
|
|
</style>
|
|
'''
|
|
|
|
TRACEBACK_WRAPPER_HTML = '''
|
|
<html>
|
|
<head>
|
|
{style}
|
|
</head>
|
|
<body>
|
|
<h1>{exc_name}</h1>
|
|
<h3><code>{exc_value}</code></h3>
|
|
<div class="tb-wrapper">
|
|
<p class="tb-header">Traceback (most recent call last):</p>
|
|
{frame_html}
|
|
<p class="summary">
|
|
<b>{exc_name}: {exc_value}</b>
|
|
while handling uri <code>{uri}</code>
|
|
</p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
'''
|
|
|
|
TRACEBACK_LINE_HTML = '''
|
|
<div class="frame-line">
|
|
<p class="frame-descriptor">
|
|
File {0.filename}, line <i>{0.lineno}</i>,
|
|
in <code><b>{0.name}</b></code>
|
|
</p>
|
|
<p class="frame-code"><code>{0.line}</code></p>
|
|
</div>
|
|
'''
|
|
|
|
INTERNAL_SERVER_ERROR_HTML = '''
|
|
<h1>Internal Server Error</h1>
|
|
<p>
|
|
The server encountered an internal error and cannot complete
|
|
your request.
|
|
</p>
|
|
'''
|
|
|
|
|
|
class SanicException(Exception):
|
|
def __init__(self, message, status_code=None):
|
|
super().__init__(message)
|
|
if status_code is not None:
|
|
self.status_code = status_code
|
|
|
|
|
|
class NotFound(SanicException):
|
|
status_code = 404
|
|
|
|
|
|
class InvalidUsage(SanicException):
|
|
status_code = 400
|
|
|
|
|
|
class ServerError(SanicException):
|
|
status_code = 500
|
|
|
|
|
|
class FileNotFound(NotFound):
|
|
status_code = 404
|
|
|
|
def __init__(self, message, path, relative_url):
|
|
super().__init__(message)
|
|
self.path = path
|
|
self.relative_url = relative_url
|
|
|
|
|
|
class RequestTimeout(SanicException):
|
|
status_code = 408
|
|
|
|
|
|
class PayloadTooLarge(SanicException):
|
|
status_code = 413
|
|
|
|
|
|
class Handler:
|
|
handlers = None
|
|
cached_handlers = None
|
|
_missing = object()
|
|
|
|
def __init__(self):
|
|
self.handlers = []
|
|
self.cached_handlers = {}
|
|
self.debug = False
|
|
|
|
def _render_traceback_html(self, exception, request):
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
frames = extract_tb(tb)
|
|
|
|
frame_html = []
|
|
for frame in frames:
|
|
frame_html.append(TRACEBACK_LINE_HTML.format(frame))
|
|
|
|
return TRACEBACK_WRAPPER_HTML.format(
|
|
style=TRACEBACK_STYLE,
|
|
exc_name=exc_type.__name__,
|
|
exc_value=exc_value,
|
|
frame_html=''.join(frame_html),
|
|
uri=request.url)
|
|
|
|
def add(self, exception, handler):
|
|
self.handlers.append((exception, handler))
|
|
|
|
def lookup(self, exception):
|
|
handler = self.cached_handlers.get(exception, self._missing)
|
|
if handler is self._missing:
|
|
for exception_class, handler in self.handlers:
|
|
if isinstance(exception, exception_class):
|
|
self.cached_handlers[type(exception)] = handler
|
|
return handler
|
|
self.cached_handlers[type(exception)] = None
|
|
handler = None
|
|
return handler
|
|
|
|
def response(self, request, exception):
|
|
"""
|
|
Fetches and executes an exception handler and returns a response object
|
|
|
|
:param request: Request
|
|
:param exception: Exception to handle
|
|
:return: Response object
|
|
"""
|
|
handler = self.lookup(exception)
|
|
try:
|
|
response = handler and handler(
|
|
request=request, exception=exception)
|
|
if response is None:
|
|
response = self.default(request=request, exception=exception)
|
|
except:
|
|
log.error(format_exc())
|
|
if self.debug:
|
|
response_message = (
|
|
'Exception raised in exception handler "{}" '
|
|
'for uri: "{}"\n{}').format(
|
|
handler.__name__, request.url, format_exc())
|
|
log.error(response_message)
|
|
return text(response_message, 500)
|
|
else:
|
|
return text('An error occurred while handling an error', 500)
|
|
return response
|
|
|
|
def default(self, request, exception):
|
|
log.error(format_exc())
|
|
if isinstance(exception, SanicException):
|
|
return text(
|
|
'Error: {}'.format(exception),
|
|
status=getattr(exception, 'status_code', 500))
|
|
elif self.debug:
|
|
html_output = self._render_traceback_html(exception, request)
|
|
|
|
response_message = (
|
|
'Exception occurred while handling uri: "{}"\n{}'.format(
|
|
request.url, format_exc()))
|
|
log.error(response_message)
|
|
return html(html_output, status=500)
|
|
else:
|
|
return html(INTERNAL_SERVER_ERROR_HTML, status=500)
|