1 # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 4 """ 5 WSGI middleware 6 7 Captures any exceptions and prints a pretty report. See the `cgitb 8 documentation <http://python.org/doc/current/lib/module-cgitb.html>`_ 9 for more. 10 """ 11 12 import cgitb 13 import six 14 from six.moves import cStringIO as StringIO 15 import sys 16 17 from paste.util import converters 18 19 class NoDefault(object): 20 pass 21 22 class CgitbMiddleware(object): 23 24 def __init__(self, app, 25 global_conf=None, 26 display=NoDefault, 27 logdir=None, 28 context=5, 29 format="html"): 30 self.app = app 31 if global_conf is None: 32 global_conf = {} 33 if display is NoDefault: 34 display = global_conf.get('debug') 35 if isinstance(display, six.string_types): 36 display = converters.asbool(display) 37 self.display = display 38 self.logdir = logdir 39 self.context = int(context) 40 self.format = format 41 42 def __call__(self, environ, start_response): 43 try: 44 app_iter = self.app(environ, start_response) 45 return self.catching_iter(app_iter, environ) 46 except: 47 exc_info = sys.exc_info() 48 start_response('500 Internal Server Error', 49 [('content-type', 'text/html')], 50 exc_info) 51 response = self.exception_handler(exc_info, environ) 52 if six.PY3: 53 response = response.encode('utf8') 54 return [response] 55 56 def catching_iter(self, app_iter, environ): 57 if not app_iter: 58 raise StopIteration 59 error_on_close = False 60 try: 61 for v in app_iter: 62 yield v 63 if hasattr(app_iter, 'close'): 64 error_on_close = True 65 app_iter.close() 66 except: 67 response = self.exception_handler(sys.exc_info(), environ) 68 if not error_on_close and hasattr(app_iter, 'close'): 69 try: 70 app_iter.close() 71 except: 72 close_response = self.exception_handler( 73 sys.exc_info(), environ) 74 response += ( 75 '<hr noshade>Error in .close():<br>%s' 76 % close_response) 77 if six.PY3: 78 response = response.encode('utf8') 79 yield response 80 81 def exception_handler(self, exc_info, environ): 82 dummy_file = StringIO() 83 hook = cgitb.Hook(file=dummy_file, 84 display=self.display, 85 logdir=self.logdir, 86 context=self.context, 87 format=self.format) 88 hook(*exc_info) 89 return dummy_file.getvalue() 90 91 def make_cgitb_middleware(app, global_conf, 92 display=NoDefault, 93 logdir=None, 94 context=5, 95 format='html'): 96 """ 97 Wraps the application in the ``cgitb`` (standard library) 98 error catcher. 99 100 display: 101 If true (or debug is set in the global configuration) 102 then the traceback will be displayed in the browser 103 104 logdir: 105 Writes logs of all errors in that directory 106 107 context: 108 Number of lines of context to show around each line of 109 source code 110 """ 111 from paste.deploy.converters import asbool 112 if display is not NoDefault: 113 display = asbool(display) 114 if 'debug' in global_conf: 115 global_conf['debug'] = asbool(global_conf['debug']) 116 return CgitbMiddleware( 117 app, global_conf=global_conf, 118 display=display, 119 logdir=logdir, 120 context=context, 121 format=format) 122