Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Fixed #16674 -- Pass exceptions to WSGI callback #133

Closed
wants to merge 1 commit into from

2 participants

Chris Glass Aymeric Augustin
Chris Glass

This patch is a attempt at making the patch attached to
https://code.djangoproject.com/ticket/16674 more github friendly, and adds
tests.

It passes raised exceptions to the WSGI start_response callback, so that
WSGI middlewares can track, log or otherwise handle excpetions (as is
done in other WSGI compliant applications, and as specified in PEP 3333).

Thanks to "jamesh" for the original fix.

Chris Glass Fixed #16674 -- Pass exceptions to WSGI callback
This patch is a attempt at making the patch attached to
https://code.djangoproject.com/ticket/16674 more github friendly.

It passes raised exceptions to the WSGI start_response callback, so that
WSGI middlewares can track, log or otherwise handle excpetions (as is
done in other WSGI compliant applications).

Thanks to "jamesh" for the original fix.

Added myself to AUTHORS
af8733f
Aymeric Augustin
Owner

This PR is stale. Since our triage options on GitHub are limited to "open" or "closed", I'm going to close it. Please re-open if you have a chance to update it.

If no one reviews it, write to the django-developers mailing list. Thanks!

Aymeric Augustin aaugustin closed this February 01, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Jun 12, 2012
Chris Glass Fixed #16674 -- Pass exceptions to WSGI callback
This patch is a attempt at making the patch attached to
https://code.djangoproject.com/ticket/16674 more github friendly.

It passes raised exceptions to the WSGI start_response callback, so that
WSGI middlewares can track, log or otherwise handle excpetions (as is
done in other WSGI compliant applications).

Thanks to "jamesh" for the original fix.

Added myself to AUTHORS
af8733f
This page is out of date. Refresh to see the latest.
1  AUTHORS
@@ -571,6 +571,7 @@ answer newbie questions, and generally made Django that much better:
571 571
     Gasper Zejn <zejn@kiberpipa.org>
572 572
     Jarek Zgoda <jarek.zgoda@gmail.com>
573 573
     Cheng Zhang
  574
+    Christopher Glass <tribaal@gmail.com>
574 575
 
575 576
 A big THANK YOU goes to:
576 577
 
25  django/core/handlers/wsgi.py
@@ -151,6 +151,7 @@ def __init__(self, environ):
151 151
             content_length = 0
152 152
         self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
153 153
         self._read_started = False
  154
+        self._wsgi_exc_info = None
154 155
 
155 156
     def get_full_path(self):
156 157
         # RFC 3986 requires query string arguments to be in the ASCII range.
@@ -225,19 +226,25 @@ def __call__(self, environ, start_response):
225 226
 
226 227
         set_script_prefix(base.get_script_name(environ))
227 228
         signals.request_started.send(sender=self.__class__)
  229
+        exc_info = None
228 230
         try:
229 231
             try:
230 232
                 request = self.request_class(environ)
231 233
             except UnicodeDecodeError:
  234
+                exc_info = sys.exc_info()
232 235
                 logger.warning('Bad Request (UnicodeDecodeError)',
233  
-                    exc_info=sys.exc_info(),
  236
+                    exc_info=exc_info,
234 237
                     extra={
235 238
                         'status_code': 400,
236 239
                     }
237 240
                 )
238 241
                 response = http.HttpResponseBadRequest()
239 242
             else:
240  
-                response = self.get_response(request)
  243
+                try:
  244
+                    response = self.get_response(request)
  245
+                finally:
  246
+                    exc_info = request._wsgi_exc_info
  247
+                    request._wsgi_exc_info = None
241 248
         finally:
242 249
             signals.request_finished.send(sender=self.__class__)
243 250
 
@@ -249,5 +256,17 @@ def __call__(self, environ, start_response):
249 256
         response_headers = [(str(k), str(v)) for k, v in response.items()]
250 257
         for c in response.cookies.values():
251 258
             response_headers.append((b'Set-Cookie', str(c.output(header=''))))
252  
-        start_response(smart_str(status), response_headers)
  259
+        try:
  260
+            start_response(smart_str(status), response_headers, exc_info)
  261
+        finally:
  262
+            exc_info = None
253 263
         return response
  264
+
  265
+    def handle_uncaught_exception(self, request, resolver, exc_info):
  266
+        # Capture the exception context so we can pass it back to __call__()
  267
+        request._wsgi_exc_info = exc_info
  268
+        try:
  269
+            return super(WSGIHandler, self).handle_uncaught_exception(
  270
+                request, resolver, exc_info)
  271
+        finally:
  272
+            exc_info = None
22  tests/regressiontests/wsgi/tests.py
@@ -16,7 +16,6 @@ def test_get_wsgi_application(self):
16 16
         """
17 17
         Verify that ``get_wsgi_application`` returns a functioning WSGI
18 18
         callable.
19  
-
20 19
         """
21 20
         application = get_wsgi_application()
22 21
 
@@ -28,7 +27,8 @@ def test_get_wsgi_application(self):
28 27
 
29 28
         response_data = {}
30 29
 
31  
-        def start_response(status, headers):
  30
+        def start_response(status, headers, exc_info=None):
  31
+            # exc_info should be optional as per PEP 3333
32 32
             response_data["status"] = status
33 33
             response_data["headers"] = headers
34 34
 
@@ -42,6 +42,24 @@ def start_response(status, headers):
42 42
             unicode(response),
43 43
             "Content-Type: text/html; charset=utf-8\n\nHello World!")
44 44
 
  45
+    def test_wsgi_exception_handling(self):
  46
+        """
  47
+        Verify that exceptions are passed to the user-supplied
  48
+        ``start_response`` callback.
  49
+        """
  50
+        application = get_wsgi_application()
  51
+        environ = RequestFactory()._base_environ(
  52
+            PATH_INFO="/exception",
  53
+            CONTENT_TYPE="text/html; charset=utf-8",
  54
+            REQUEST_METHOD="GET"
  55
+            )
  56
+
  57
+        def start_response(status, headers, exc_info=None):
  58
+            # exc_info should be optional as per PEP 3333
  59
+            self.assertNotEqual(exc_info, None)
  60
+
  61
+        application(environ, start_response)
  62
+
45 63
 
46 64
 class GetInternalWSGIApplicationTest(unittest.TestCase):
47 65
     @override_settings(WSGI_APPLICATION="regressiontests.wsgi.wsgi.application")
6  tests/regressiontests/wsgi/urls.py
@@ -4,7 +4,11 @@
4 4
 def helloworld(request):
5 5
     return HttpResponse("Hello World!")
6 6
 
  7
+def will_raise(request):
  8
+    raise Exception()
  9
+
7 10
 urlpatterns = patterns(
8 11
     "",
9  
-    url("^$", helloworld)
  12
+    url("^$", helloworld),
  13
+    url("^exception$", will_raise)
10 14
     )
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.