the5fire
- 资深开发工程师
- 写了十年多代码
- 多年Python工程师招聘面试官
这个解读来源于一个读者的反馈,于是花了几分钟看了下这部分源码,打算用十分钟的时间写一下,预计阅读需要 5 分钟。
自定义异常页面
Django 提供了常见的错误的页面,比如
-
说用户访问了一个不存在的路径,引发的 404
-
系统发生了一个异常,出现了 500
一个好的网站应该可以给用户友好的信息提示,比如:“服务器提了一个问题”之类的,然后给用户一个引导。对于商业网站需要注意的是
错误页面的流量也是流量
,应该有明确的引导。
在 Django 中定义这类处理很简单,只需要在 urls.py 中配置:
handler404 = Handler404.as_view()
handler500 = Handler50x.as_view()
当然你需要定义这里面的 Handler50x:
class Handler404(CommonViewMixin, TemplateView):
template_name = '404.html'
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context, status=404)
class Handler50x(CommonViewMixin, TemplateView):
template_name = '50x.html'
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context, status=500)
这样就可以简单的控制出错时展示给用户的页面了。需要注意的是,这个配置只会在非 Debug 模式下有效。
Django Error Handler 源码解析
要看这部分源码的第一步是判断 Django 可能会在哪处理这个异常。有很多方法,这里是说一种,从请求的入口开始撸。
注意我看到版本是 Django 2.0.1
1 WSGI Handler 的部分
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __init__(self, *args, **kwargs):
super(WSGIHandler, self).__init__(*args, **kwargs)
self.load_middleware()
def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
request = self.request_class(environ)
response = self.get_response(request)
2 BaseHandler 中的 get_response
def get_response(self, request):
"""Return an HttpResponse object for the given HttpRequest."""
set_urlconf(settings.ROOT_URLCONF)
response = self._middleware_chain(request)
response._closable_objects.append(request)
if not getattr(response, 'is_rendered', True) and callable(getattr(response, 'render', None)):
response = response.render()
if response.status_code == 404:
logger.warning(
'Not Found: %s', request.path,
extra={'status_code': 404, 'request': request},
)
return response
3 被包装的 _middleware_chain
def load_middleware(self):
"""
Populate middleware lists from settings.MIDDLEWARE.
Must be called after the environment is fixed (see __call__ in subclasses).
"""
self._request_middleware = []
self._view_middleware = []
self._template_response_middleware = []
self._response_middleware = []
self._exception_middleware = []
handler = convert_exception_to_response(self._get_response)
for middleware_path in reversed(settings.MIDDLEWARE):
middleware = import_string(middleware_path)
handler = convert_exception_to_response(mw_instance)
self._middleware_chain = handler
4 具体处理异常的部分
def convert_exception_to_response(get_response):
"""
Wrap the given get_response callable in exception-to-response conversion.
All exceptions will be converted. All known 4xx exceptions (Http404,
PermissionDenied, MultiPartParserError, SuspiciousOperation) will be
converted to the appropriate response, and all other exceptions will be
converted to 500 responses.
This decorator is automatically applied to all middleware to ensure that
no middleware leaks an exception and that the next middleware in the stack
can rely on getting a response instead of an exception.
"""
@wraps(get_response)
def inner(request):
try:
response = get_response(request)
except Exception as exc:
response = response_for_exception(request, exc)
return response
return inner
def response_for_exception(request, exc):
if isinstance(exc, Http404):
if settings.DEBUG:
response = debug.technical_404_response(request, exc)
else:
response = get_exception_response(request, get_resolver(get_urlconf()), 404, exc)
else:
signals.got_request_exception.send(sender=None, request=request)
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
if not getattr(response, 'is_rendered', True) and callable(getattr(response, 'render', None)):
response = response.render()
return response
5 异常处理逻辑