Index: django/test/client.py
===================================================================
--- django/test/client.py	(revision 7320)
+++ django/test/client.py	(working copy)
@@ -23,29 +23,18 @@
     Uses the WSGI interface to compose requests, but returns
     the raw HttpResponse object
     """
+    request_class = WSGIRequest
+
     def __call__(self, environ):
         from django.conf import settings
-        from django.core import signals
 
         # Set up middleware if needed. We couldn't do this earlier, because
         # settings weren't available.
         if self._request_middleware is None:
             self.load_middleware()
 
-        dispatcher.send(signal=signals.request_started)
-        try:
-            request = WSGIRequest(environ)
-            response = self.get_response(request)
+        return self.handle_request(environ)
 
-            # Apply response middleware
-            for middleware_method in self._response_middleware:
-                response = middleware_method(request, response)
-            response = self.apply_response_fixes(request, response)
-        finally:
-            dispatcher.send(signal=signals.request_finished)
-
-        return response
-
 def store_rendered_templates(store, signal, sender, template, context):
     "A utility function for storing templates and contexts that are rendered"
     store.setdefault('template',[]).append(template)
@@ -94,7 +83,7 @@
     ])
     return '\r\n'.join(lines)
 
-class Client:
+class Client(object):
     """
     A class that can act as a client for testing purposes.
 
@@ -117,6 +106,7 @@
         self.defaults = defaults
         self.cookies = SimpleCookie()
         self.exc_info = None
+        self.reraise_exceptions = True
 
     def store_exc_info(self, *args, **kwargs):
         """
@@ -178,7 +168,7 @@
                 raise
 
         # Look for a signalled exception and reraise it
-        if self.exc_info:
+        if self.exc_info and self.reraise_exceptions:
             raise self.exc_info[1], None, self.exc_info[2]
 
         # Save the client and request that stimulated the response
Index: django/core/handlers/wsgi.py
===================================================================
--- django/core/handlers/wsgi.py	(revision 7320)
+++ django/core/handlers/wsgi.py	(working copy)
@@ -6,9 +6,7 @@
     from StringIO import StringIO
 
 from django import http
-from django.core import signals
 from django.core.handlers.base import BaseHandler
-from django.dispatch import dispatcher
 from django.utils import datastructures
 from django.utils.encoding import force_unicode
 
@@ -195,22 +193,8 @@
                 self.load_middleware()
             self.initLock.release()
 
-        dispatcher.send(signal=signals.request_started)
-        try:
-            try:
-                request = self.request_class(environ)
-            except UnicodeDecodeError:
-                response = http.HttpResponseBadRequest()
-            else:
-                response = self.get_response(request)
+        response = self.handle_request(environ)
 
-                # Apply response middleware
-                for middleware_method in self._response_middleware:
-                    response = middleware_method(request, response)
-                response = self.apply_response_fixes(request, response)
-        finally:
-            dispatcher.send(signal=signals.request_finished)
-
         try:
             status_text = STATUS_CODE_TEXT[response.status_code]
         except KeyError:
Index: django/core/handlers/base.py
===================================================================
--- django/core/handlers/base.py	(revision 7320)
+++ django/core/handlers/base.py	(working copy)
@@ -3,7 +3,21 @@
 from django import http
 from django.core import signals
 from django.dispatch import dispatcher
+from django.core.urlresolvers import RegexURLResolver
 
+
+def resolver(request):
+    """
+    Returns a RegexURLResolver for the request's urlconf.
+
+    If the request does not have a urlconf object, then the default of
+    settings.ROOT_URLCONF is used.
+    """
+    from django.conf import settings
+    urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
+    return RegexURLResolver(r'^/', urlconf)
+
+
 class BaseHandler(object):
     # Changes that are always applied to a response (in this order).
     response_fixes = [http.fix_location_header,
@@ -28,16 +42,16 @@
             try:
                 dot = middleware_path.rindex('.')
             except ValueError:
-                raise exceptions.ImproperlyConfigured, '%s isn\'t a middleware module' % middleware_path
+                raise exceptions.ImproperlyConfigured('%s isn\'t a middleware module' % middleware_path)
             mw_module, mw_classname = middleware_path[:dot], middleware_path[dot+1:]
             try:
                 mod = __import__(mw_module, {}, {}, [''])
             except ImportError, e:
-                raise exceptions.ImproperlyConfigured, 'Error importing middleware %s: "%s"' % (mw_module, e)
+                raise exceptions.ImproperlyConfigured('Error importing middleware %s: "%s"' % (mw_module, e))
             try:
                 mw_class = getattr(mod, mw_classname)
             except AttributeError:
-                raise exceptions.ImproperlyConfigured, 'Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname)
+                raise exceptions.ImproperlyConfigured('Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname))
 
             try:
                 mw_instance = mw_class()
@@ -53,58 +67,141 @@
             if hasattr(mw_instance, 'process_exception'):
                 self._exception_middleware.insert(0, mw_instance.process_exception)
 
-    def get_response(self, request):
-        "Returns an HttpResponse object for the given HttpRequest"
-        from django.core import exceptions, urlresolvers
-        from django.core.mail import mail_admins
-        from django.conf import settings
+    def handle_request(self, request):
+        """
+        Takes a request and returns a response.
 
-        # Apply request middleware
+        Processing occurs according to the following diagram:
+
+            Request                    Response
+               |                           |
+          +----------+                +----------+
+          | Request  |                | Response |<--------+
+          |Middleware|---Response?--->|Middleware|         |
+          +----------+                +----------+   404/500 Response
+               |                           |               |
+        ---------Request Exception Handler-----------------+---------
+               |                           |
+          +---------+                      +<-------+
+          | URLConf |                      |        |
+          +---------+                      |   +----------+
+               |                           |   |Exception |
+          +----------+                     |   |Middleware|
+          |   View   |                     |   +----------+
+          |Middleware|-----Response?------>+        |
+          +----------+                     |    Exception
+               |                           |        |
+        ----------View Exception Handler------------+----------------
+               |                           |
+            +------+                       |
+            | View |-----Response--------->+
+            +------+
+        """
+        dispatcher.send(signal=signals.request_started)
+        try:
+            try:
+                request = self.request_class(request)
+            except UnicodeDecodeError:
+                response = http.HttpResponseBadRequest()
+            else:
+                # See if the request middleware returns a response.
+                response = self.get_response(self.apply_request_middleware,
+                                             request)
+                # If not, then call the view.
+                if not response:
+                    response = self.get_response(self.run_view_processing,
+                                                 request)
+            # All responses go through the response middleware.
+            response = self.get_response(self.apply_response_middleware,
+                                         request, response)
+            response = self.apply_response_fixes(request, response)
+            return response
+        finally:
+            dispatcher.send(signal=signals.request_finished)
+
+    def apply_request_middleware(self, request):
+        """
+        Applies request middleware one at a time until a response is returned.
+        If none of request middleware returns a response, then return None.
+        """
         for middleware_method in self._request_middleware:
             response = middleware_method(request)
             if response:
                 return response
 
-        # Get urlconf from request object, if available.  Otherwise use default.
-        urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
+    def run_view_processing(self, request):
+        """
+        Apply view middleware and call view.
+        """
+        callback, callback_args, callback_kwargs = resolver(request).resolve(request.path)
 
-        resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
+        # Apply view middleware
+        for middleware_method in self._view_middleware:
+            response = middleware_method(request, callback, callback_args, callback_kwargs)
+            if response:
+                return response
+
         try:
-            callback, callback_args, callback_kwargs = resolver.resolve(request.path)
-
-            # Apply view middleware
-            for middleware_method in self._view_middleware:
-                response = middleware_method(request, callback, callback_args, callback_kwargs)
+            response = callback(request, *callback_args, **callback_kwargs)
+        except Exception, e:
+            # If the view raised an exception, run it through exception
+            # middleware, and if the exception middleware returns a
+            # response, use that. Otherwise, reraise the exception.
+            for middleware_method in self._exception_middleware:
+                response = middleware_method(request, e)
                 if response:
                     return response
+            raise
 
+        # Complain if the view returned None (a common error).
+        if response is None:
             try:
-                response = callback(request, *callback_args, **callback_kwargs)
-            except Exception, e:
-                # If the view raised an exception, run it through exception
-                # middleware, and if the exception middleware returns a
-                # response, use that. Otherwise, reraise the exception.
-                for middleware_method in self._exception_middleware:
-                    response = middleware_method(request, e)
-                    if response:
-                        return response
-                raise
+                view_name = callback.func_name # If it's a function
+            except AttributeError:
+                view_name = callback.__class__.__name__ + '.__call__' # If it's a class
+            raise ValueError("The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name))
 
-            # Complain if the view returned None (a common error).
+        return response
+
+    def apply_response_middleware(self, request, response):
+        """
+        Applies all response middleware to the response.
+        """
+        for middleware_method in self._response_middleware:
+            response = middleware_method(request, response)
+        return response
+
+    def get_response(self, method, request, response=None):
+        """
+        Returns an HttpResponse by calling the passed method with the given
+        HttpRequest object.  If an HttpResponse is passed, it will also be
+        passed in the call to method.  Exceptions raised by calling method
+        will be handled like so:
+
+        * Http404 exception - If settings.DEBUG is True, the
+          technical_404_response debug view will be called.  If settings.DEBUG
+          is False, the URL resolver's handler404 view will be called.
+        * PermissionDenied exception - return HttpResponseForbidden.
+        * SystemExit exception - ignored.
+        * All other exceptions - If settings.DEBUG is True, the
+          technical_500_response debug view will be called.  If settings.DEBUG
+          is False, the URL resolver's handler500 view will be called.
+        """
+        from django.core import exceptions
+        from django.core.mail import mail_admins
+        from django.conf import settings
+
+        try:
             if response is None:
-                try:
-                    view_name = callback.func_name # If it's a function
-                except AttributeError:
-                    view_name = callback.__class__.__name__ + '.__call__' # If it's a class
-                raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
-
-            return response
+                return method(request)
+            else:
+                return method(request, response)
         except http.Http404, e:
             if settings.DEBUG:
                 from django.views import debug
                 return debug.technical_404_response(request, e)
             else:
-                callback, param_dict = resolver.resolve404()
+                callback, param_dict = resolver(request).resolve404()
                 return callback(request, **param_dict)
         except exceptions.PermissionDenied:
             return http.HttpResponseForbidden('<h1>Permission denied</h1>')
@@ -128,7 +225,7 @@
                 message = "%s\n\n%s" % (self._get_traceback(exc_info), request_repr)
                 mail_admins(subject, message, fail_silently=True)
                 # Return an HttpResponse that displays a friendly error message.
-                callback, param_dict = resolver.resolve500()
+                callback, param_dict = resolver(request).resolve500()
                 return callback(request, **param_dict)
 
     def _get_traceback(self, exc_info=None):
@@ -145,4 +242,3 @@
         for func in self.response_fixes:
             response = func(request, response)
         return response
-
Index: django/core/handlers/modpython.py
===================================================================
--- django/core/handlers/modpython.py	(revision 7320)
+++ django/core/handlers/modpython.py	(working copy)
@@ -2,9 +2,7 @@
 from pprint import pformat
 
 from django import http
-from django.core import signals
 from django.core.handlers.base import BaseHandler
-from django.dispatch import dispatcher
 from django.utils import datastructures
 from django.utils.encoding import force_unicode, smart_str
 
@@ -152,22 +150,8 @@
         if self._request_middleware is None:
             self.load_middleware()
 
-        dispatcher.send(signal=signals.request_started)
-        try:
-            try:
-                request = self.request_class(req)
-            except UnicodeDecodeError:
-                response = http.HttpResponseBadRequest()
-            else:
-                response = self.get_response(request)
+        response = self.handle_request(req)
 
-                # Apply response middleware
-                for middleware_method in self._response_middleware:
-                    response = middleware_method(request, response)
-                response = self.apply_response_fixes(request, response)
-        finally:
-            dispatcher.send(signal=signals.request_finished)
-
         # Convert our custom HttpResponse object back into the mod_python req.
         req.content_type = response['Content-Type']
         for key, value in response.items():
Index: tests/regressiontests/handler_exceptions/views.py
===================================================================
--- tests/regressiontests/handler_exceptions/views.py	(revision 0)
+++ tests/regressiontests/handler_exceptions/views.py	(revision 0)
@@ -0,0 +1,7 @@
+from django.http import HttpResponse
+
+def good_view(request):
+    return HttpResponse("good view")
+
+def bad_view(request):
+    raise Exception("View raised exception")
Index: tests/regressiontests/handler_exceptions/__init__.py
===================================================================
Index: tests/regressiontests/handler_exceptions/tests.py
===================================================================
--- tests/regressiontests/handler_exceptions/tests.py	(revision 0)
+++ tests/regressiontests/handler_exceptions/tests.py	(revision 0)
@@ -0,0 +1,149 @@
+from django.test import TestCase
+from django.test.client import Client, ClientHandler
+
+class GoodMiddleware(object):
+    def process_request(self, *args, **kwargs):
+        pass
+    def process_view(self, *args, **kwargs):
+        pass
+    def process_exception(self, *args, **kwargs):
+        pass
+    def process_response(self, request, response):
+        return response
+
+
+class MiddlewareException(Exception): pass
+class RequestMiddlewareException(MiddlewareException): pass
+class ViewMiddlewareException(MiddlewareException): pass
+class ExceptionMiddlewareException(MiddlewareException): pass
+class ResponseMiddlewareException(MiddlewareException): pass
+
+
+class BadRequestMiddleware(object):
+    def process_request(self, *args, **kwargs):
+        raise RequestMiddlewareException
+
+class BadViewMiddleware(object):
+    def process_view(self, *args, **kwargs):
+        raise ViewMiddlewareException
+
+class BadExceptionMiddleware(object):
+    def process_exception(self, *args, **kwargs):
+        raise ExceptionMiddlewareException
+
+class BadResponseMiddleware(object):
+    def process_response(self, *args, **kwargs):
+        raise ResponseMiddlewareException
+
+
+class MiddlewareResettingClientHandler(ClientHandler):
+    """
+    A handler that clears loaded middleware after each request.
+    """
+    def __call__(self, *args, **kwargs):
+        response = super(MiddlewareResettingClientHandler, self).__call__(*args, **kwargs)
+        # ClientHandler's __call__ method will load middleware if
+        # self._request_middleware is None.  We set it to None here so that
+        # middleware will be reloaded next request.
+        self._request_middleware = None
+        return response
+
+
+class HandlerClient(Client):
+    """
+    A custom Client that doesn't reraise request-handling exceptions and uses
+    the MiddlewareResettingClientHandler.
+    """
+    def __init__(self, *args, **kwargs):
+        super(HandlerClient, self).__init__(*args, **kwargs)
+        self.reraise_exceptions = False
+        self.handler = MiddlewareResettingClientHandler()
+
+
+class HandlerExceptionTests(TestCase):
+    def setUp(self):
+        self.client = HandlerClient()
+        self.default_middleware = None
+        # Combinations that will raise errors.
+        self.bad_combinations = (
+            # Middleware, request path
+            ('BadRequestMiddleware', '/good_view/', RequestMiddlewareException),
+            ('BadViewMiddleware', '/good_view/', ViewMiddlewareException),
+            ('BadResponseMiddleware', '/good_view/', ResponseMiddlewareException),
+            ('BadRequestMiddleware', '/bad_view/', RequestMiddlewareException),
+            ('BadViewMiddleware', '/bad_view/', ViewMiddlewareException),
+            ('BadExceptionMiddleware', '/bad_view/', ExceptionMiddlewareException),
+            ('BadResponseMiddleware', '/bad_view/', ResponseMiddlewareException),
+        )
+        # Combinations that will not raise errors.
+        self.good_combinations = (
+            ('GoodMiddleware', '/good_view/'),
+            # Exception middleware doesn't get run if the view doesn't raise
+            # an exception.
+            ('BadExceptionMiddleware', '/good_view/'),
+        )
+
+    def setup_middleware(self, middleware):
+        """
+        Append middleware to list of installed middleware.
+        """
+        from django.conf import settings
+        # If we haven't saved the default middleware, do so now.
+        if self.default_middleware is None:
+            self.default_middleware = settings.MIDDLEWARE_CLASSES
+        middleware_path = 'regressiontests.handler_exceptions.tests.' + middleware
+        settings.MIDDLEWARE_CLASSES += (middleware_path,)
+
+    def reset_middleware(self):
+        """
+        Restores middleware to the default installed middleware.
+        """
+        from django.conf import settings
+        settings.MIDDLEWARE_CLASSES = self.default_middleware
+
+    def test_exception_handling(self):
+        """
+        Tests that exceptions raised in middleware and views are properly
+        caught and render error templates.
+        """
+        # Just for good measure, check that the good combination doesn't raise
+        # an error.
+        for middleware, path in self.good_combinations:
+            self.setup_middleware(middleware)
+            response = self.client.get('/handler_exceptions' + path)
+            self.assertContains(response, 'good view')
+            self.reset_middleware()
+        # Now test that all the bad combinations render the 500 template.
+        for middleware, path, exception in self.bad_combinations:
+            self.setup_middleware(middleware)
+            response = self.client.get('/handler_exceptions' + path)
+            self.assertContains(response, '500 Error', status_code=500)
+            self.assertTemplateUsed(response, '500.html')
+            self.reset_middleware()
+
+    def test_exception_raising(self):
+        """
+        Tests that the correct exceptions bubble up from middleware with errors.
+        """
+        # Turn on client's reraising of exceptions for this test.
+        self.client.reraise_exceptions = True
+        for middleware, path, exception in self.bad_combinations:
+            self.setup_middleware(middleware)
+            fail = False
+            try:
+                self.client.get('/handler_exceptions' + path)
+                # If above didn't raise an exception, then test failed.
+                fail = True
+            except exception:
+                # We got the exception we were looking for, test passes.
+                pass
+            except Exception:
+                # We got an exception that we wore not expecting.
+                from sys import exc_info
+                unexpected_exception = exc_info()[0]
+                self.fail("Expected %s to be raised, but got %s"
+                          % (exception, unexpected_exception))
+            if fail:
+                self.fail("Expected %s to be raised.  Middleware: %s, Path: %s"
+                          % (exception, middleware, path))
+            self.reset_middleware()
Index: tests/regressiontests/handler_exceptions/models.py
===================================================================
--- tests/regressiontests/handler_exceptions/models.py	(revision 0)
+++ tests/regressiontests/handler_exceptions/models.py	(revision 0)
@@ -0,0 +1 @@
+# models file for tests to run.
Index: tests/regressiontests/handler_exceptions/urls.py
===================================================================
--- tests/regressiontests/handler_exceptions/urls.py	(revision 0)
+++ tests/regressiontests/handler_exceptions/urls.py	(revision 0)
@@ -0,0 +1,8 @@
+from django.conf.urls.defaults import *
+
+import views
+
+urlpatterns = patterns('',
+    (r'^good_view/$', views.good_view),
+    (r'^bad_view/$', views.bad_view),
+)
Index: tests/urls.py
===================================================================
--- tests/urls.py	(revision 7320)
+++ tests/urls.py	(working copy)
@@ -18,5 +18,8 @@
     # test urlconf for middleware tests
     (r'^middleware/', include('regressiontests.middleware.urls')),
 
+    # urlconf for handler_exceptions tests.
+    (r'^handler_exceptions/', include('regressiontests.handler_exceptions.urls')),
+
     (r'^utils/', include('regressiontests.utils.urls')),
 )
