mirror of
https://github.com/django/django.git
synced 2025-10-24 06:06:09 +00:00
Refs #26601 -- Improved backwards-compatibility of DEP 5 middleware exception handling.
This commit is contained in:
@@ -1,20 +1,10 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.flatpages.views import flatpage
|
||||
from django.http import Http404
|
||||
from django.middleware.exception import ExceptionMiddleware
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
|
||||
class FlatpageFallbackMiddleware(ExceptionMiddleware):
|
||||
|
||||
def __init__(self, get_response=None):
|
||||
# This override makes get_response optional during the
|
||||
# MIDDLEWARE_CLASSES deprecation.
|
||||
super(FlatpageFallbackMiddleware, self).__init__(get_response)
|
||||
|
||||
def __call__(self, request):
|
||||
response = super(FlatpageFallbackMiddleware, self).__call__(request)
|
||||
return self.process_response(request, response)
|
||||
|
||||
class FlatpageFallbackMiddleware(MiddlewareMixin):
|
||||
def process_response(self, request, response):
|
||||
if response.status_code != 404:
|
||||
return response # No need to check for a flatpage for non-404 responses.
|
||||
|
||||
@@ -6,11 +6,10 @@ from django.conf import settings
|
||||
from django.contrib.redirects.models import Redirect
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.middleware.exception import ExceptionMiddleware
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
|
||||
class RedirectFallbackMiddleware(ExceptionMiddleware):
|
||||
|
||||
class RedirectFallbackMiddleware(MiddlewareMixin):
|
||||
# Defined as class-level attributes to be subclassing-friendly.
|
||||
response_gone_class = http.HttpResponseGone
|
||||
response_redirect_class = http.HttpResponsePermanentRedirect
|
||||
@@ -23,10 +22,6 @@ class RedirectFallbackMiddleware(ExceptionMiddleware):
|
||||
)
|
||||
super(RedirectFallbackMiddleware, self).__init__(get_response)
|
||||
|
||||
def __call__(self, request):
|
||||
response = super(RedirectFallbackMiddleware, self).__call__(request)
|
||||
return self.process_response(request, response)
|
||||
|
||||
def process_response(self, request, response):
|
||||
# No need to check for a redirect for non-404 responses.
|
||||
if response.status_code != 404:
|
||||
|
||||
@@ -9,12 +9,15 @@ from django.conf import settings
|
||||
from django.core import signals
|
||||
from django.core.exceptions import ImproperlyConfigured, MiddlewareNotUsed
|
||||
from django.db import connections, transaction
|
||||
from django.middleware.exception import ExceptionMiddleware
|
||||
from django.urls import get_resolver, get_urlconf, set_urlconf
|
||||
from django.utils import six
|
||||
from django.utils.deprecation import RemovedInDjango20Warning
|
||||
from django.utils.module_loading import import_string
|
||||
from django.views import debug
|
||||
|
||||
from .exception import (
|
||||
convert_exception_to_response, get_exception_response,
|
||||
handle_uncaught_exception,
|
||||
)
|
||||
|
||||
logger = logging.getLogger('django.request')
|
||||
|
||||
@@ -48,7 +51,7 @@ class BaseHandler(object):
|
||||
"deprecated. Update your middleware and use settings.MIDDLEWARE "
|
||||
"instead.", RemovedInDjango20Warning
|
||||
)
|
||||
handler = self._legacy_get_response
|
||||
handler = convert_exception_to_response(self._legacy_get_response)
|
||||
for middleware_path in settings.MIDDLEWARE_CLASSES:
|
||||
mw_class = import_string(middleware_path)
|
||||
try:
|
||||
@@ -72,7 +75,7 @@ class BaseHandler(object):
|
||||
if hasattr(mw_instance, 'process_exception'):
|
||||
self._exception_middleware.insert(0, mw_instance.process_exception)
|
||||
else:
|
||||
handler = self._get_response
|
||||
handler = convert_exception_to_response(self._get_response)
|
||||
for middleware_path in reversed(settings.MIDDLEWARE):
|
||||
middleware = import_string(middleware_path)
|
||||
try:
|
||||
@@ -94,10 +97,10 @@ class BaseHandler(object):
|
||||
self._view_middleware.insert(0, mw_instance.process_view)
|
||||
if hasattr(mw_instance, 'process_template_response'):
|
||||
self._template_response_middleware.append(mw_instance.process_template_response)
|
||||
if hasattr(mw_instance, 'process_exception'):
|
||||
self._exception_middleware.append(mw_instance.process_exception)
|
||||
|
||||
handler = mw_instance
|
||||
|
||||
handler = ExceptionMiddleware(handler, self)
|
||||
handler = convert_exception_to_response(mw_instance)
|
||||
|
||||
# We only assign to this when initialization is complete as it is used
|
||||
# as a flag for initialization being complete.
|
||||
@@ -111,25 +114,7 @@ class BaseHandler(object):
|
||||
return view
|
||||
|
||||
def get_exception_response(self, request, resolver, status_code, exception):
|
||||
try:
|
||||
callback, param_dict = resolver.resolve_error_handler(status_code)
|
||||
# Unfortunately, inspect.getargspec result is not trustable enough
|
||||
# depending on the callback wrapping in decorators (frequent for handlers).
|
||||
# Falling back on try/except:
|
||||
try:
|
||||
response = callback(request, **dict(param_dict, exception=exception))
|
||||
except TypeError:
|
||||
warnings.warn(
|
||||
"Error handlers should accept an exception parameter. Update "
|
||||
"your code as this parameter will be required in Django 2.0",
|
||||
RemovedInDjango20Warning, stacklevel=2
|
||||
)
|
||||
response = callback(request, **param_dict)
|
||||
except Exception:
|
||||
signals.got_request_exception.send(sender=self.__class__, request=request)
|
||||
response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
|
||||
|
||||
return response
|
||||
return get_exception_response(request, resolver, status_code, exception, self.__class__)
|
||||
|
||||
def get_response(self, request):
|
||||
"""Return an HttpResponse object for the given HttpRequest."""
|
||||
@@ -138,6 +123,8 @@ class BaseHandler(object):
|
||||
|
||||
response = self._middleware_chain(request)
|
||||
|
||||
# This block is only needed for legacy MIDDLEWARE_CLASSES; if
|
||||
# MIDDLEWARE is used, self._response_middleware will be empty.
|
||||
try:
|
||||
# Apply response middleware, regardless of the response
|
||||
for middleware_method in self._response_middleware:
|
||||
@@ -168,6 +155,11 @@ class BaseHandler(object):
|
||||
return response
|
||||
|
||||
def _get_response(self, request):
|
||||
"""
|
||||
Resolve and call the view, then apply view, exception, and
|
||||
template_response middleware. This method is everything that happens
|
||||
inside the request/response middleware.
|
||||
"""
|
||||
response = None
|
||||
|
||||
if hasattr(request, 'urlconf'):
|
||||
@@ -237,35 +229,14 @@ class BaseHandler(object):
|
||||
raise
|
||||
|
||||
def handle_uncaught_exception(self, request, resolver, exc_info):
|
||||
"""
|
||||
Processing for any otherwise uncaught exceptions (those that will
|
||||
generate HTTP 500 responses). Can be overridden by subclasses who want
|
||||
customised 500 handling.
|
||||
|
||||
Be *very* careful when overriding this because the error could be
|
||||
caused by anything, so assuming something like the database is always
|
||||
available would be an error.
|
||||
"""
|
||||
if settings.DEBUG_PROPAGATE_EXCEPTIONS:
|
||||
raise
|
||||
|
||||
logger.error(
|
||||
'Internal Server Error: %s', request.path,
|
||||
exc_info=exc_info,
|
||||
extra={'status_code': 500, 'request': request},
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
return debug.technical_500_response(request, *exc_info)
|
||||
|
||||
# If Http500 handler is not installed, re-raise last exception
|
||||
if resolver.urlconf_module is None:
|
||||
six.reraise(*exc_info)
|
||||
# Return an HttpResponse that displays a friendly error message.
|
||||
callback, param_dict = resolver.resolve_error_handler(500)
|
||||
return callback(request, **param_dict)
|
||||
"""Allow subclasses to override uncaught exception handling."""
|
||||
return handle_uncaught_exception(request, resolver, exc_info)
|
||||
|
||||
def _legacy_get_response(self, request):
|
||||
"""
|
||||
Apply process_request() middleware and call the main _get_response(),
|
||||
if needed. Used only for legacy MIDDLEWARE_CLASSES.
|
||||
"""
|
||||
response = None
|
||||
# Apply request middleware
|
||||
for middleware_method in self._request_middleware:
|
||||
|
||||
135
django/core/handlers/exception.py
Normal file
135
django/core/handlers/exception.py
Normal file
@@ -0,0 +1,135 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import warnings
|
||||
from functools import wraps
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import signals
|
||||
from django.core.exceptions import PermissionDenied, SuspiciousOperation
|
||||
from django.http import Http404
|
||||
from django.http.multipartparser import MultiPartParserError
|
||||
from django.urls import get_resolver, get_urlconf
|
||||
from django.utils import six
|
||||
from django.utils.decorators import available_attrs
|
||||
from django.utils.deprecation import RemovedInDjango20Warning
|
||||
from django.utils.encoding import force_text
|
||||
from django.views import debug
|
||||
|
||||
logger = logging.getLogger('django.request')
|
||||
|
||||
|
||||
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, assigned=available_attrs(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)
|
||||
|
||||
elif isinstance(exc, PermissionDenied):
|
||||
logger.warning(
|
||||
'Forbidden (Permission denied): %s', request.path,
|
||||
extra={'status_code': 403, 'request': request},
|
||||
)
|
||||
response = get_exception_response(request, get_resolver(get_urlconf()), 403, exc)
|
||||
|
||||
elif isinstance(exc, MultiPartParserError):
|
||||
logger.warning(
|
||||
'Bad request (Unable to parse request body): %s', request.path,
|
||||
extra={'status_code': 400, 'request': request},
|
||||
)
|
||||
response = get_exception_response(request, get_resolver(get_urlconf()), 400, exc)
|
||||
|
||||
elif isinstance(exc, SuspiciousOperation):
|
||||
# The request logger receives events for any problematic request
|
||||
# The security logger receives events for all SuspiciousOperations
|
||||
security_logger = logging.getLogger('django.security.%s' % exc.__class__.__name__)
|
||||
security_logger.error(
|
||||
force_text(exc),
|
||||
extra={'status_code': 400, 'request': request},
|
||||
)
|
||||
if settings.DEBUG:
|
||||
response = debug.technical_500_response(request, *sys.exc_info(), status_code=400)
|
||||
else:
|
||||
response = get_exception_response(request, get_resolver(get_urlconf()), 400, exc)
|
||||
|
||||
elif isinstance(exc, SystemExit):
|
||||
# Allow sys.exit() to actually exit. See tickets #1023 and #4701
|
||||
raise
|
||||
|
||||
else:
|
||||
signals.got_request_exception.send(sender=None, request=request)
|
||||
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def get_exception_response(request, resolver, status_code, exception, sender=None):
|
||||
try:
|
||||
callback, param_dict = resolver.resolve_error_handler(status_code)
|
||||
# Unfortunately, inspect.getargspec result is not trustable enough
|
||||
# depending on the callback wrapping in decorators (frequent for handlers).
|
||||
# Falling back on try/except:
|
||||
try:
|
||||
response = callback(request, **dict(param_dict, exception=exception))
|
||||
except TypeError:
|
||||
warnings.warn(
|
||||
"Error handlers should accept an exception parameter. Update "
|
||||
"your code as this parameter will be required in Django 2.0",
|
||||
RemovedInDjango20Warning, stacklevel=2
|
||||
)
|
||||
response = callback(request, **param_dict)
|
||||
except Exception:
|
||||
signals.got_request_exception.send(sender=sender, request=request)
|
||||
response = handle_uncaught_exception(request, resolver, sys.exc_info())
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def handle_uncaught_exception(request, resolver, exc_info):
|
||||
"""
|
||||
Processing for any otherwise uncaught exceptions (those that will
|
||||
generate HTTP 500 responses).
|
||||
"""
|
||||
if settings.DEBUG_PROPAGATE_EXCEPTIONS:
|
||||
raise
|
||||
|
||||
logger.error(
|
||||
'Internal Server Error: %s', request.path,
|
||||
exc_info=exc_info,
|
||||
extra={'status_code': 500, 'request': request},
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
return debug.technical_500_response(request, *exc_info)
|
||||
|
||||
# If Http500 handler is not installed, reraise the last exception.
|
||||
if resolver.urlconf_module is None:
|
||||
six.reraise(*exc_info)
|
||||
# Return an HttpResponse that displays a friendly error message.
|
||||
callback, param_dict = resolver.resolve_error_handler(500)
|
||||
return callback(request, **param_dict)
|
||||
@@ -1,78 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import signals
|
||||
from django.core.exceptions import PermissionDenied, SuspiciousOperation
|
||||
from django.http import Http404
|
||||
from django.http.multipartparser import MultiPartParserError
|
||||
from django.urls import get_resolver, get_urlconf
|
||||
from django.utils.encoding import force_text
|
||||
from django.views import debug
|
||||
|
||||
logger = logging.getLogger('django.request')
|
||||
|
||||
|
||||
class ExceptionMiddleware(object):
|
||||
"""
|
||||
Convert selected exceptions to HTTP responses.
|
||||
|
||||
For example, convert Http404 to a 404 response either through handler404
|
||||
or through the debug view if settings.DEBUG=True. To ensure that
|
||||
exceptions raised by other middleware are converted to the appropriate
|
||||
response, this middleware is always automatically applied as the outermost
|
||||
middleware.
|
||||
"""
|
||||
def __init__(self, get_response, handler=None):
|
||||
from django.core.handlers.base import BaseHandler
|
||||
self.get_response = get_response
|
||||
self.handler = handler or BaseHandler()
|
||||
|
||||
def __call__(self, request):
|
||||
try:
|
||||
response = self.get_response(request)
|
||||
except Http404 as exc:
|
||||
if settings.DEBUG:
|
||||
response = debug.technical_404_response(request, exc)
|
||||
else:
|
||||
response = self.handler.get_exception_response(request, get_resolver(get_urlconf()), 404, exc)
|
||||
|
||||
except PermissionDenied as exc:
|
||||
logger.warning(
|
||||
'Forbidden (Permission denied): %s', request.path,
|
||||
extra={'status_code': 403, 'request': request},
|
||||
)
|
||||
response = self.handler.get_exception_response(request, get_resolver(get_urlconf()), 403, exc)
|
||||
|
||||
except MultiPartParserError as exc:
|
||||
logger.warning(
|
||||
'Bad request (Unable to parse request body): %s', request.path,
|
||||
extra={'status_code': 400, 'request': request},
|
||||
)
|
||||
response = self.handler.get_exception_response(request, get_resolver(get_urlconf()), 400, exc)
|
||||
|
||||
except SuspiciousOperation as exc:
|
||||
# The request logger receives events for any problematic request
|
||||
# The security logger receives events for all SuspiciousOperations
|
||||
security_logger = logging.getLogger('django.security.%s' % exc.__class__.__name__)
|
||||
security_logger.error(
|
||||
force_text(exc),
|
||||
extra={'status_code': 400, 'request': request},
|
||||
)
|
||||
if settings.DEBUG:
|
||||
return debug.technical_500_response(request, *sys.exc_info(), status_code=400)
|
||||
|
||||
response = self.handler.get_exception_response(request, get_resolver(get_urlconf()), 400, exc)
|
||||
|
||||
except SystemExit:
|
||||
# Allow sys.exit() to actually exit. See tickets #1023 and #4701
|
||||
raise
|
||||
|
||||
except Exception: # Handle everything else.
|
||||
# Get the exception info now, in case another exception is thrown later.
|
||||
signals.got_request_exception.send(sender=self.handler.__class__, request=request)
|
||||
response = self.handler.handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
|
||||
|
||||
return response
|
||||
@@ -3,13 +3,13 @@
|
||||
from django.conf import settings
|
||||
from django.conf.urls.i18n import is_language_prefix_patterns_used
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.middleware.exception import ExceptionMiddleware
|
||||
from django.urls import get_script_prefix, is_valid_path
|
||||
from django.utils import translation
|
||||
from django.utils.cache import patch_vary_headers
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
|
||||
class LocaleMiddleware(ExceptionMiddleware):
|
||||
class LocaleMiddleware(MiddlewareMixin):
|
||||
"""
|
||||
This is a very simple middleware that parses a request
|
||||
and decides what translation object to install in the current
|
||||
@@ -19,17 +19,6 @@ class LocaleMiddleware(ExceptionMiddleware):
|
||||
"""
|
||||
response_redirect_class = HttpResponseRedirect
|
||||
|
||||
def __init__(self, get_response=None):
|
||||
# This override makes get_response optional during the
|
||||
# MIDDLEWARE_CLASSES deprecation.
|
||||
super(LocaleMiddleware, self).__init__(get_response)
|
||||
|
||||
def __call__(self, request):
|
||||
response = self.process_request(request)
|
||||
if not response:
|
||||
response = super(LocaleMiddleware, self).__call__(request)
|
||||
return self.process_response(request, response)
|
||||
|
||||
def process_request(self, request):
|
||||
urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
|
||||
i18n_patterns_used, prefixed_default_language = is_language_prefix_patterns_used(urlconf)
|
||||
|
||||
@@ -124,13 +124,7 @@ class MiddlewareMixin(object):
|
||||
if hasattr(self, 'process_request'):
|
||||
response = self.process_request(request)
|
||||
if not response:
|
||||
try:
|
||||
response = self.get_response(request)
|
||||
except Exception as e:
|
||||
if hasattr(self, 'process_exception'):
|
||||
return self.process_exception(request, e)
|
||||
else:
|
||||
raise
|
||||
response = self.get_response(request)
|
||||
if hasattr(self, 'process_response'):
|
||||
response = self.process_response(request, response)
|
||||
return response
|
||||
|
||||
Reference in New Issue
Block a user