summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorCarl Meyer <carl@oddbird.net>2016-06-14 00:41:58 -0700
committerTim Graham <timograham@gmail.com>2016-06-17 15:55:55 -0400
commit69de988f9237fe978c5fca363ca2d2622f8e2ccd (patch)
tree85e2b35e3173b62f395d78a018a454d999097606 /django
parent34fbec3cb48f41dda8daecf2e8330233621fa059 (diff)
[1.10.x] Refs #26601 -- Improved backwards-compatibility of DEP 5 middleware exception handling.
Backport of 7d1b69dbe7f72ac04d2513f0468fe2146231b286 from master
Diffstat (limited to 'django')
-rw-r--r--django/contrib/flatpages/middleware.py14
-rw-r--r--django/contrib/redirects/middleware.py9
-rw-r--r--django/core/handlers/base.py77
-rw-r--r--django/core/handlers/exception.py135
-rw-r--r--django/middleware/exception.py78
-rw-r--r--django/middleware/locale.py15
-rw-r--r--django/utils/deprecation.py8
7 files changed, 166 insertions, 170 deletions
diff --git a/django/contrib/flatpages/middleware.py b/django/contrib/flatpages/middleware.py
index 4c6760535d..9c6a7273cc 100644
--- a/django/contrib/flatpages/middleware.py
+++ b/django/contrib/flatpages/middleware.py
@@ -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.
diff --git a/django/contrib/redirects/middleware.py b/django/contrib/redirects/middleware.py
index e3ee982a8b..f5c9f85588 100644
--- a/django/contrib/redirects/middleware.py
+++ b/django/contrib/redirects/middleware.py
@@ -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:
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index fbe7a8b8a0..55802f6a65 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -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:
diff --git a/django/core/handlers/exception.py b/django/core/handlers/exception.py
new file mode 100644
index 0000000000..9edf457ad1
--- /dev/null
+++ b/django/core/handlers/exception.py
@@ -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)
diff --git a/django/middleware/exception.py b/django/middleware/exception.py
deleted file mode 100644
index 437a82cf26..0000000000
--- a/django/middleware/exception.py
+++ /dev/null
@@ -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
diff --git a/django/middleware/locale.py b/django/middleware/locale.py
index 1a95dbc8b2..65a3893e9a 100644
--- a/django/middleware/locale.py
+++ b/django/middleware/locale.py
@@ -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)
diff --git a/django/utils/deprecation.py b/django/utils/deprecation.py
index 7627426080..351bd1d27d 100644
--- a/django/utils/deprecation.py
+++ b/django/utils/deprecation.py
@@ -121,13 +121,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