OLD | NEW |
(Empty) | |
| 1 from __future__ import unicode_literals |
| 2 |
| 3 import logging |
| 4 import sys |
| 5 import warnings |
| 6 from functools import wraps |
| 7 |
| 8 from django.conf import settings |
| 9 from django.core import signals |
| 10 from django.core.exceptions import ( |
| 11 PermissionDenied, RequestDataTooBig, SuspiciousOperation, |
| 12 TooManyFieldsSent, |
| 13 ) |
| 14 from django.http import Http404 |
| 15 from django.http.multipartparser import MultiPartParserError |
| 16 from django.urls import get_resolver, get_urlconf |
| 17 from django.utils.decorators import available_attrs |
| 18 from django.utils.deprecation import RemovedInDjango20Warning |
| 19 from django.utils.encoding import force_text |
| 20 from django.views import debug |
| 21 |
| 22 logger = logging.getLogger('django.request') |
| 23 |
| 24 |
| 25 def convert_exception_to_response(get_response): |
| 26 """ |
| 27 Wrap the given get_response callable in exception-to-response conversion. |
| 28 |
| 29 All exceptions will be converted. All known 4xx exceptions (Http404, |
| 30 PermissionDenied, MultiPartParserError, SuspiciousOperation) will be |
| 31 converted to the appropriate response, and all other exceptions will be |
| 32 converted to 500 responses. |
| 33 |
| 34 This decorator is automatically applied to all middleware to ensure that |
| 35 no middleware leaks an exception and that the next middleware in the stack |
| 36 can rely on getting a response instead of an exception. |
| 37 """ |
| 38 @wraps(get_response, assigned=available_attrs(get_response)) |
| 39 def inner(request): |
| 40 try: |
| 41 response = get_response(request) |
| 42 except Exception as exc: |
| 43 response = response_for_exception(request, exc) |
| 44 return response |
| 45 return inner |
| 46 |
| 47 |
| 48 def response_for_exception(request, exc): |
| 49 if isinstance(exc, Http404): |
| 50 if settings.DEBUG: |
| 51 response = debug.technical_404_response(request, exc) |
| 52 else: |
| 53 response = get_exception_response(request, get_resolver(get_urlconf(
)), 404, exc) |
| 54 |
| 55 elif isinstance(exc, PermissionDenied): |
| 56 logger.warning( |
| 57 'Forbidden (Permission denied): %s', request.path, |
| 58 extra={'status_code': 403, 'request': request}, |
| 59 ) |
| 60 response = get_exception_response(request, get_resolver(get_urlconf()),
403, exc) |
| 61 |
| 62 elif isinstance(exc, MultiPartParserError): |
| 63 logger.warning( |
| 64 'Bad request (Unable to parse request body): %s', request.path, |
| 65 extra={'status_code': 400, 'request': request}, |
| 66 ) |
| 67 response = get_exception_response(request, get_resolver(get_urlconf()),
400, exc) |
| 68 |
| 69 elif isinstance(exc, SuspiciousOperation): |
| 70 if isinstance(exc, (RequestDataTooBig, TooManyFieldsSent)): |
| 71 # POST data can't be accessed again, otherwise the original |
| 72 # exception would be raised. |
| 73 request._mark_post_parse_error() |
| 74 |
| 75 # The request logger receives events for any problematic request |
| 76 # The security logger receives events for all SuspiciousOperations |
| 77 security_logger = logging.getLogger('django.security.%s' % exc.__class__
.__name__) |
| 78 security_logger.error( |
| 79 force_text(exc), |
| 80 extra={'status_code': 400, 'request': request}, |
| 81 ) |
| 82 if settings.DEBUG: |
| 83 response = debug.technical_500_response(request, *sys.exc_info(), st
atus_code=400) |
| 84 else: |
| 85 response = get_exception_response(request, get_resolver(get_urlconf(
)), 400, exc) |
| 86 |
| 87 elif isinstance(exc, SystemExit): |
| 88 # Allow sys.exit() to actually exit. See tickets #1023 and #4701 |
| 89 raise |
| 90 |
| 91 else: |
| 92 signals.got_request_exception.send(sender=None, request=request) |
| 93 response = handle_uncaught_exception(request, get_resolver(get_urlconf()
), sys.exc_info()) |
| 94 |
| 95 # Force a TemplateResponse to be rendered. |
| 96 if not getattr(response, 'is_rendered', True) and callable(getattr(response,
'render', None)): |
| 97 response = response.render() |
| 98 |
| 99 return response |
| 100 |
| 101 |
| 102 def get_exception_response(request, resolver, status_code, exception, sender=Non
e): |
| 103 try: |
| 104 callback, param_dict = resolver.resolve_error_handler(status_code) |
| 105 # Unfortunately, inspect.getargspec result is not trustable enough |
| 106 # depending on the callback wrapping in decorators (frequent for handler
s). |
| 107 # Falling back on try/except: |
| 108 try: |
| 109 response = callback(request, **dict(param_dict, exception=exception)
) |
| 110 except TypeError: |
| 111 warnings.warn( |
| 112 "Error handlers should accept an exception parameter. Update " |
| 113 "your code as this parameter will be required in Django 2.0", |
| 114 RemovedInDjango20Warning, stacklevel=2 |
| 115 ) |
| 116 response = callback(request, **param_dict) |
| 117 except Exception: |
| 118 signals.got_request_exception.send(sender=sender, request=request) |
| 119 response = handle_uncaught_exception(request, resolver, sys.exc_info()) |
| 120 |
| 121 return response |
| 122 |
| 123 |
| 124 def handle_uncaught_exception(request, resolver, exc_info): |
| 125 """ |
| 126 Processing for any otherwise uncaught exceptions (those that will |
| 127 generate HTTP 500 responses). |
| 128 """ |
| 129 if settings.DEBUG_PROPAGATE_EXCEPTIONS: |
| 130 raise |
| 131 |
| 132 logger.error( |
| 133 'Internal Server Error: %s', request.path, |
| 134 exc_info=exc_info, |
| 135 extra={'status_code': 500, 'request': request}, |
| 136 ) |
| 137 |
| 138 if settings.DEBUG: |
| 139 return debug.technical_500_response(request, *exc_info) |
| 140 |
| 141 # Return an HttpResponse that displays a friendly error message. |
| 142 callback, param_dict = resolver.resolve_error_handler(500) |
| 143 return callback(request, **param_dict) |
OLD | NEW |