summaryrefslogtreecommitdiff
path: root/paste
diff options
context:
space:
mode:
authorIan Bicking <ian@ianbicking.org>2005-11-06 10:38:00 +0000
committerIan Bicking <ian@ianbicking.org>2005-11-06 10:38:00 +0000
commitcc3e81c627ccb8ecbdb8ea0a12dadcb1b24d1027 (patch)
treeacef354087a8bca5703fc29f2687dd6d686b562c /paste
parent93a2652ae218410ca36b40b8411138b33c7d0b83 (diff)
downloadpaste-git-cc3e81c627ccb8ecbdb8ea0a12dadcb1b24d1027.tar.gz
Further refactoring and expansion of the eval exception middleware; functions now, but not that fancy yet
Diffstat (limited to 'paste')
-rw-r--r--paste/evalexception/debug.js3
-rw-r--r--paste/evalexception/media/debug.js26
-rw-r--r--paste/evalexception/middleware.py237
-rw-r--r--paste/exceptions/formatter.py60
4 files changed, 271 insertions, 55 deletions
diff --git a/paste/evalexception/debug.js b/paste/evalexception/debug.js
deleted file mode 100644
index 9729b04..0000000
--- a/paste/evalexception/debug.js
+++ /dev/null
@@ -1,3 +0,0 @@
-function show_frame (a, b) {
- foo;
-}
diff --git a/paste/evalexception/media/debug.js b/paste/evalexception/media/debug.js
new file mode 100644
index 0000000..f7b6c6f
--- /dev/null
+++ b/paste/evalexception/media/debug.js
@@ -0,0 +1,26 @@
+function show_frame(anchor) {
+ var framecount = anchor.getAttribute('framecount');
+ var expanded = anchor.expanded;
+ if (expanded) {
+ MochiKit.DOM.hideElement(anchor.expandedElement);
+ anchor.expanded = false;
+ return false;
+ }
+ anchor.expanded = true;
+ if (anchor.expandedElement) {
+ MochiKit.DOM.showElement(anchor.expandedElement);
+ return false;
+ }
+ var url = debug_base
+ + '/_debug/show_frame?framecount=' + framecount
+ + '&debugcount=' + debug_count;
+ var d = MochiKit.Async.doSimpleXMLHttpRequest(url);
+ d.addCallbacks(function (data) {
+ var el = MochiKit.DOM.DIV();
+ anchor.parentNode.insertBefore(el, anchor.nextSibling);
+ el.innerHTML = data.responseText;
+ anchor.expandedElement = el;
+ }, function (error) {
+ alert('An error occurred: "' + error + '" for URL: ' + url);
+ });
+}
diff --git a/paste/evalexception/middleware.py b/paste/evalexception/middleware.py
index fa36d4e..54d43fb 100644
--- a/paste/evalexception/middleware.py
+++ b/paste/evalexception/middleware.py
@@ -1,30 +1,170 @@
import sys
+import os
import threading
+import cgi
+import traceback
+from cStringIO import StringIO
+import pprint
+import itertools
+import time
from paste.exceptions import errormiddleware, formatter, collector
+from paste import wsgilib
+from paste import urlparser
+from paste import httpexceptions
+import cgi
+
+limit = 200
+
+def html_quote(v):
+ if v is None:
+ return ''
+ return cgi.escape(str(v), 1)
+
+def wsgiapp():
+ """
+ Turns a function or method into a
+ """
+ def decorator(func):
+ def application(*args):
+ if len(args) == 3:
+ environ = args[1]
+ start_response = args[2]
+ args = [args[0]]
+ else:
+ environ, start_response = args
+ args = []
+ fs = cgi.FieldStorage(
+ fp=environ['wsgi.input'],
+ environ=environ,
+ keep_blank_values=1)
+ form = {}
+ for name in fs.keys():
+ value = fs[name]
+ if not value.filename:
+ value = value.value
+ if name in form:
+ if isinstance(form[name], list):
+ form[name].append(value)
+ else:
+ form[name] = [form[name], value]
+ else:
+ form[name] = value
+ headers = HeaderDict({'content-type': 'text/html',
+ 'status': '200 OK'})
+ form['environ'] = environ
+ form['headers'] = headers
+ res = func(*args, **form)
+ status = headers['status']
+ del headers['status']
+ start_response(status, headers.headeritems())
+ return [res]
+ application.exposed = True
+ return application
+ return decorator
+
+def get_debug_info(func):
+ def replacement(self, **form):
+ try:
+ if 'debugcount' not in form:
+ raise ValueError('You must provide a debugcount parameter')
+ debugcount = form.pop('debugcount')
+ try:
+ debugcount = int(debugcount)
+ except ValueError:
+ raise ValueError('Bad value for debugcount')
+ if debugcount not in self.debug_infos:
+ raise ValueError('Debug %s no longer found (maybe it has expired?)' % debugcount)
+ debug_info = self.debug_infos[debugcount]
+ return func(self, debug_info=debug_info, **form)
+ except ValueError, e:
+ form['headers']['status'] = '500 Server Error'
+ return '<html>There was an error: %s</html>' % e
+ return replacement
+
+
+class HeaderDict(dict):
+
+ def __getitem__(self, key):
+ return dict.__getitem__(self, key.lower())
+
+ def __setitem__(self, key, value):
+ dict.__setitem__(self, key.lower(), value)
+
+ def __delitem__(self, key):
+ dict.__delitem__(self, key.lower())
+
+ def add(self, key, value):
+ key = key.lower()
+ if key in self:
+ if isinstance(self[key], list):
+ self[key].append(value)
+ else:
+ self[key] = [self[key], value]
+ else:
+ self[key] = value
+
+ def headeritems(self):
+ result = []
+ for key in self:
+ if isinstance(self[key], list):
+ for v in self[key]:
+ result.append((key, v))
+ else:
+ result.append((key, self[key]))
+ return result
+
+debug_counter = itertools.count(int(time.time()))
class EvalException(object):
def __init__(self, application, global_conf=None):
self.application = application
- self.debugging = False
- # This is a single-threaded middleware:
- self.lock = threading.Lock()
- self.exc_info = None
+ self.debug_infos = {}
def __call__(self, environ, start_response):
assert not environ['wsgi.multiprocess'], (
"The EvalException middleware is not usable in a multi-process environment")
- self.lock.acquire()
- try:
- if self.debugging:
- return self.debug(environ, start_response)
- else:
- return self.respond(environ, start_response)
- finally:
- self.lock.release()
+ if environ.get('PATH_INFO', '').startswith('/_debug/'):
+ return self.debug(environ, start_response)
+ else:
+ return self.respond(environ, start_response)
- def debug(self, *args):
- return self.respond(*args)
+ def debug(self, environ, start_response):
+ assert wsgilib.path_info_pop(environ) == '_debug'
+ next_part = wsgilib.path_info_pop(environ)
+ method = getattr(self, next_part, None)
+ if not method:
+ return wsgilib.error_response_app(
+ '404 Not Found', '%r not found' % next_part)(
+ environ, start_response)
+ if not getattr(method, 'exposed', False):
+ return wsgilib.error_response_app(
+ '403 Forbidden', '%r not allowed' % next_part)(
+ environ, start_response)
+ return method(environ, start_response)
+
+ def media(self, environ, start_response):
+ app = urlparser.StaticURLParser(
+ os.path.join(os.path.dirname(__file__), 'media'))
+ return app(environ, start_response)
+ media.exposed = True
+
+ def mochikit(self, environ, start_response):
+ app = urlparser.StaticURLParser(
+ os.path.join(os.path.dirname(__file__), 'mochikit', 'MochiKit'))
+ return app(environ, start_response)
+ mochikit.exposed = True
+
+ @wsgiapp()
+ @get_debug_info
+ def show_frame(self, framecount, debug_info, **kw):
+ frame = debug_info.frames[int(framecount)]
+ vars = frame.tb_frame.f_locals
+ if vars:
+ local_vars = make_table(vars)
+ else:
+ local_vars = 'No local vars'
+ return local_vars
def respond(self, environ, start_response):
base_path = environ['SCRIPT_NAME']
@@ -42,20 +182,23 @@ class EvalException(object):
app_iter = self.application(environ, detect_start_response)
return self.catching_iter(app_iter, environ)
except:
- self.exc_info = exc_info = sys.exc_info()
- self.debugging = True
for expected in environ.get('paste.expected_exceptions', []):
if issubclass(exc_info[0], expected):
raise
+ exc_info = sys.exc_info()
+ count = debug_counter.next()
+ debug_info = DebugInfo(count, exc_info)
+ assert count not in self.debug_infos
+ self.debug_infos[count] = debug_info
if not started:
start_response('500 Internal Server Error',
[('content-type', 'text/html')],
exc_info)
# @@: it would be nice to deal with bad content types here
exc_data = collector.collect_exception(*exc_info)
- html = format_eval_html(exc_data, base_path)
+ html = format_eval_html(exc_data, base_path, count)
head_html = (formatter.error_css + formatter.hide_display_js)
- head_html += self.eval_javascript(base_path)
+ head_html += self.eval_javascript(base_path, count)
page = error_template % {
'head_html': head_html,
'body': html}
@@ -85,21 +228,38 @@ class EvalException(object):
% close_response)
yield response
- def eval_javascript(self, base_path):
- f = open(os.path.join(os.path.dirname(__file__),
- 'evalexception.js'))
- js = f.read()
- f.close()
- return ('<script type="text/javascript">\n'
- 'debug_base = %r;\n' % base_path
- + js
- + '\n</script>\n')
+ def eval_javascript(self, base_path, counter):
+ return ('<script type="text/javascript" src="%s/_debug/mochikit/MochiKit.js"></script>\n'
+ '<script type="text/javascript" src="%s/_debug/media/debug.js"></script>\n'
+ '<script type="text/javascript">\n'
+ 'debug_base = %r;\n'
+ 'debug_count = %r;\n'
+ '\n</script>\n'
+ % (base_path, base_path, base_path, counter))
+
+class DebugInfo(object):
+
+ def __init__(self, counter, exc_info):
+ self.counter = counter
+ self.exc_type, self.exc_value, self.tb = exc_info
+ __exception_formatter__ = 1
+ self.frames = []
+ n = 0
+ tb = self.tb
+ while tb is not None and (limit is None or n < limit):
+ if tb.tb_frame.f_locals.get('__exception_formatter__'):
+ # Stop recursion. @@: should make a fake ExceptionFrame
+ break
+ self.frames.append(tb)
+ tb = tb.tb_next
+ n += 1
class EvalHTMLFormatter(formatter.HTMLFormatter):
- def __init__(self, base_path, **kw):
+ def __init__(self, base_path, counter, **kw):
super(EvalHTMLFormatter, self).__init__(**kw)
self.base_path = base_path
+ self.counter = counter
self.framecount = -1
def format_source_line(self, filename, modname, lineno, name):
@@ -107,15 +267,32 @@ class EvalHTMLFormatter(formatter.HTMLFormatter):
self, filename, modname, lineno, name)
self.framecount += 1
return (line +
- ' <a href="#" frameid="%s" onClick="show_frame(this)">[+]</a>'
+ ' <a href="#" framecount="%s" onClick="show_frame(this)">[+]</a>'
% self.framecount)
-def format_eval_html(exc_data, base_path):
+def make_table(items):
+ if isinstance(items, dict):
+ items = items.items()
+ items.sort()
+ rows = []
+ for name, value in items:
+ out = StringIO()
+ pprint.pprint(value, out)
+ value = html_quote(out.getvalue())
+ value = formatter.make_pre_wrappable(value)
+ rows.append('<tr><td>%s</td><td><pre style="overflow: auto">%s</pre><td></tr>'
+ % (html_quote(name), value))
+ return '<table border="1">%s</table>' % (
+ '\n'.join(rows))
+
+def format_eval_html(exc_data, base_path, counter):
short_er = EvalHTMLFormatter(
base_path=base_path,
+ counter=counter,
include_reusable=False).format_collected_data(exc_data)
long_er = EvalHTMLFormatter(
base_path=base_path,
+ counter=counter,
show_hidden_frames=True,
show_extra_data=False,
include_reusable=False).format_collected_data(exc_data)
diff --git a/paste/exceptions/formatter.py b/paste/exceptions/formatter.py
index eb71ce4..d50de94 100644
--- a/paste/exceptions/formatter.py
+++ b/paste/exceptions/formatter.py
@@ -313,30 +313,10 @@ class HTMLFormatter(TextFormatter):
% (odd and 'odd' or 'even', self.quote(name)))
table.append(
'<td><tt>%s</tt></td></tr>'
- % self.make_wrappable(self.quote(value)))
+ % make_wrappable(self.quote(value)))
table.append('</table>')
return '\n'.join(table)
- def make_wrappable(self, html, wrap_limit=60,
- split_on=';?&@!$#-/\\"\''):
- # Currently using <wbr>, maybe should use &#8203;
- # http://www.cs.tut.fi/~jkorpela/html/nobr.html
- words = html.split()
- new_words = []
- for word in words:
- if len(word) > wrap_limit:
- for char in split_on:
- if char in word:
- words = [
- self.make_wrappable(w, wrap_limit=wrap_limit,
- split_on=split_on)
- for w in word.split(char, 1)]
- new_words.append('<wbr>'.join(words))
- break
- else:
- new_words.append(word)
- return ' '.join(new_words)
-
hide_display_js = r'''
<script type="text/javascript">
function hide_display(id) {
@@ -513,4 +493,40 @@ def _str2html(src, strip=False, indent_subsequent=0):
lambda m: '&nbsp;'*(len(m.group(0))-1) + ' ', src)
return src
-
+def make_wrappable(html, wrap_limit=60,
+ split_on=';?&@!$#-/\\"\''):
+ # Currently using <wbr>, maybe should use &#8203;
+ # http://www.cs.tut.fi/~jkorpela/html/nobr.html
+ words = html.split()
+ new_words = []
+ for word in words:
+ if len(word) > wrap_limit:
+ for char in split_on:
+ if char in word:
+ words = [
+ make_wrappable(w, wrap_limit=wrap_limit,
+ split_on=split_on)
+ for w in word.split(char, 1)]
+ new_words.append('<wbr>'.join(words))
+ break
+ else:
+ new_words.append(word)
+ return ' '.join(new_words)
+
+def make_pre_wrappable(html, wrap_limit=60,
+ split_on=';?&@!$#-/\\"\''):
+ """
+ Like ``make_wrappable()`` but intended for text that will
+ go in a ``<pre>`` block, so wrap on a line-by-line basis.
+ """
+ lines = html.splitlines()
+ new_lines = []
+ for line in lines:
+ if len(line) > wrap_limit:
+ for char in split_on:
+ if char in line:
+ parts = line.split(char)
+ line = '<wbr>'.join(parts)
+ break
+ new_lines.append(line)
+ return '\n'.join(lines)