Ignore:
Timestamp:
Dec 20, 2009, 9:45:52 PM (15 years ago)
Author:
broder
Message:

Full error handling

Location:
package_branches/invirt-web/cherrypy-rebased
Files:
2 added
2 deleted
4 edited
1 moved

Legend:

Unmodified
Added
Removed
  • package_branches/invirt-web/cherrypy-rebased/code/main.py

    r2692 r2693  
    99import random
    1010import sha
    11 import simplejson
    1211import sys
    1312import time
     
    1514import socket
    1615import cherrypy
     16from cherrypy import _cperror
    1717from StringIO import StringIO
    18 def revertStandardError():
    19     """Move stderr to stdout, and return the contents of the old stderr."""
    20     errio = sys.stderr
    21     if not isinstance(errio, StringIO):
    22         return ''
    23     sys.stderr = sys.stdout
    24     errio.seek(0)
    25     return errio.read()
    2618
    2719def printError():
     
    3426    atexit.register(printError)
    3527
    36 import templates
    37 from Cheetah.Template import Template
    3828import validation
    3929import cache_acls
     
    4636from invirt.common import InvalidInput, CodeError
    4737
    48 from view import View
     38from view import View, revertStandardError
    4939
    5040class InvirtUnauthWeb(View):
     
    5949        connect()
    6050        self._cp_config['tools.require_login.on'] = True
     51        self._cp_config['tools.catch_stderr.on'] = True
    6152        self._cp_config['tools.mako.imports'] = ['from invirt.config import structs as config',
    6253                                                 'from invirt import database']
     54        self._cp_config['request.error_response'] = self.handle_error
     55
     56    @cherrypy.expose
     57    @cherrypy.tools.mako(filename="/invalid.mako")
     58    def invalidInput(self):
     59        """Print an error page when an InvalidInput exception occurs"""
     60        err = cherrypy.request.prev.params["err"]
     61        emsg = cherrypy.request.prev.params["emsg"]
     62        d = dict(err_field=err.err_field,
     63                 err_value=str(err.err_value), stderr=emsg,
     64                 errorMessage=str(err))
     65        return d
     66
     67    @cherrypy.expose
     68    @cherrypy.tools.mako(filename="/error.mako")
     69    def error(self):
     70        #op, username, fields, err, emsg, traceback):
     71        """Print an error page when an exception occurs"""
     72        op = cherrypy.request.prev.path_info
     73        username = cherrypy.request.login
     74        err = cherrypy.request.prev.params["err"]
     75        emsg = cherrypy.request.prev.params["emsg"]
     76        traceback = cherrypy.request.prev.params["traceback"]
     77        d = dict(op = op, user=username, fields=cherrypy.request.prev.params,
     78                 errorMessage=str(err), stderr=emsg, traceback=traceback)
     79        error_raw = cherrypy.tools.mako.callable.get_lookup(**cherrypy.tools.mako._merged_args()).get_template("/error_raw.mako")
     80        details = error_raw.render(**d)
     81        exclude = config.web.errormail_exclude
     82        if username not in exclude and '*' not in exclude:
     83            send_error_mail('xvm error on %s for %s: %s' % (op, cherrypy.request.login, err),
     84                            details)
     85        d['details'] = details
     86        return d
    6387
    6488    def __getattr__(self, name):
     
    7195        else:
    7296            return super(InvirtWeb, self).__getattr__(name)
     97
     98    def handle_error(self):
     99        err = sys.exc_info()[1]
     100        if isinstance(err, InvalidInput):
     101            e = revertStandardError()
     102            cherrypy.request.params['err'] = err
     103            cherrypy.request.params['emsg'] = e
     104            raise cherrypy.InternalRedirect('/invalidInput')
     105        if not cherrypy.request.prev or 'err' not in cherrypy.request.prev.params:
     106            e = revertStandardError()
     107            cherrypy.request.params['err'] = err
     108            cherrypy.request.params['emsg'] = e
     109            cherrypy.request.params['traceback'] = _cperror.format_exc()
     110            raise cherrypy.InternalRedirect('/error')
     111        # fall back to cherrypy default error page
     112        cherrypy.HTTPError(500).set_response()
    73113
    74114    @cherrypy.expose
     
    201241    def errortest(self):
    202242        """Throw an error, to test the error-tracing mechanisms."""
     243        print >>sys.stderr, "look ma, it's a stderr"
    203244        raise RuntimeError("test of the emergency broadcast system")
    204245
     
    303344                    raise
    304345                print >> sys.stderr, err
    305                 result = err
     346                result = str(err)
    306347            else:
    307348                result = 'Success!'
     
    318359    machine = MachineView()
    319360
    320 def pathSplit(path):
    321     if path.startswith('/'):
    322         path = path[1:]
    323     i = path.find('/')
    324     if i == -1:
    325         i = len(path)
    326     return path[:i], path[i:]
    327 
    328361class Checkpoint:
    329362    def __init__(self):
     
    340373
    341374checkpoint = Checkpoint()
    342 
    343 def makeErrorPre(old, addition):
    344     if addition is None:
    345         return
    346     if old:
    347         return old[:-6]  + '\n----\n' + str(addition) + '</pre>'
    348     else:
    349         return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
    350 
    351 Template.database = database
    352 Template.config = config
    353 Template.err = None
    354 
    355 class JsonDict:
    356     """Class to store a dictionary that will be converted to JSON"""
    357     def __init__(self, **kws):
    358         self.data = kws
    359         if 'err' in kws:
    360             err = kws['err']
    361             del kws['err']
    362             self.addError(err)
    363 
    364     def __str__(self):
    365         return simplejson.dumps(self.data)
    366 
    367     def addError(self, text):
    368         """Add stderr text to be displayed on the website."""
    369         self.data['err'] = \
    370             makeErrorPre(self.data.get('err'), text)
    371375
    372376class Defaults:
     
    388392        for key in kws:
    389393            setattr(self, key, kws[key])
    390 
    391 
    392 
    393 DEFAULT_HEADERS = {'Content-Type': 'text/html'}
    394 
    395 def invalidInput(op, username, fields, err, emsg):
    396     """Print an error page when an InvalidInput exception occurs"""
    397     d = dict(op=op, user=username, err_field=err.err_field,
    398              err_value=str(err.err_value), stderr=emsg,
    399              errorMessage=str(err))
    400     return templates.invalid(searchList=[d])
    401394
    402395def hasVnc(status):
     
    568561    return dict(machine=machine)
    569562
    570 
    571 def badOperation(u, s, p, e):
    572     """Function called when accessing an unknown URI."""
    573     return ({'Status': '404 Not Found'}, 'Invalid operation.')
    574 
    575563def infoDict(username, state, machine):
    576564    """Get the variables used by info.tmpl."""
     
    661649    return d
    662650
    663 mapping = dict()
    664 
    665 def printHeaders(headers):
    666     """Print a dictionary as HTTP headers."""
    667     for key, value in headers.iteritems():
    668         print '%s: %s' % (key, value)
    669     print
    670 
    671651def send_error_mail(subject, body):
    672652    import subprocess
     
    685665    p.wait()
    686666
    687 def show_error(op, username, fields, err, emsg, traceback):
    688     """Print an error page when an exception occurs"""
    689     d = dict(op=op, user=username, fields=fields,
    690              errorMessage=str(err), stderr=emsg, traceback=traceback)
    691     details = templates.error_raw(searchList=[d])
    692     exclude = config.web.errormail_exclude
    693     if username not in exclude and '*' not in exclude:
    694         send_error_mail('xvm error on %s for %s: %s' % (op, username, err),
    695                         details)
    696     d['details'] = details
    697     return templates.error(searchList=[d])
    698 
    699 def handler(username, state, path, fields):
    700     operation, path = pathSplit(path)
    701     if not operation:
    702         operation = 'list'
    703     print 'Starting', operation
    704     fun = mapping.get(operation, badOperation)
    705     return fun(username, state, path, fields)
    706 
    707 class App:
    708     def __init__(self, environ, start_response):
    709         self.environ = environ
    710         self.start = start_response
    711 
    712         self.username = getUser(environ)
    713         self.state = State(self.username)
    714         self.state.environ = environ
    715 
    716         random.seed() #sigh
    717 
    718     def __iter__(self):
    719         start_time = time.time()
    720         database.clear_cache()
    721         sys.stderr = StringIO()
    722         fields = cgi.FieldStorage(fp=self.environ['wsgi.input'], environ=self.environ)
    723         operation = self.environ.get('PATH_INFO', '')
    724         if not operation:
    725             self.start("301 Moved Permanently", [('Location', './')])
    726             return
    727         if self.username is None:
    728             operation = 'unauth'
    729 
    730         try:
    731             checkpoint.checkpoint('Before')
    732             output = handler(self.username, self.state, operation, fields)
    733             checkpoint.checkpoint('After')
    734 
    735             headers = dict(DEFAULT_HEADERS)
    736             if isinstance(output, tuple):
    737                 new_headers, output = output
    738                 headers.update(new_headers)
    739             e = revertStandardError()
    740             if e:
    741                 if hasattr(output, 'addError'):
    742                     output.addError(e)
    743                 else:
    744                     # This only happens on redirects, so it'd be a pain to get
    745                     # the message to the user.  Maybe in the response is useful.
    746                     output = output + '\n\nstderr:\n' + e
    747             output_string =  str(output)
    748             checkpoint.checkpoint('output as a string')
    749         except Exception, err:
    750             if not fields.has_key('js'):
    751                 if isinstance(err, InvalidInput):
    752                     self.start('200 OK', [('Content-Type', 'text/html')])
    753                     e = revertStandardError()
    754                     yield str(invalidInput(operation, self.username, fields,
    755                                            err, e))
    756                     return
    757             import traceback
    758             self.start('500 Internal Server Error',
    759                        [('Content-Type', 'text/html')])
    760             e = revertStandardError()
    761             s = show_error(operation, self.username, fields,
    762                            err, e, traceback.format_exc())
    763             yield str(s)
    764             return
    765         status = headers.setdefault('Status', '200 OK')
    766         del headers['Status']
    767         self.start(status, headers.items())
    768         yield output_string
    769         if fields.has_key('timedebug'):
    770             yield '<pre>%s</pre>' % cgi.escape(str(checkpoint))
    771 
    772 def constructor():
    773     connect()
    774     return App
    775 
    776 def main():
    777     from flup.server.fcgi_fork import WSGIServer
    778     WSGIServer(constructor()).run()
    779 
    780 if __name__ == '__main__':
    781     main()
     667random.seed()
  • package_branches/invirt-web/cherrypy-rebased/code/templates/error.mako

    r2692 r2693  
    1 #from skeleton import skeleton
    2 #extends skeleton
     1<%page expression_filter="h"/>
     2<%inherit file="skeleton.mako" />
    33
    4 #def title
     4<%def name="title()">
    55ERROR!
    6 #end def
     6</%def>
    77
    8 #def body
    98<p>Uh-oh!  We experienced an error.  Sorry about that.  We've gotten
    109mail about it.</p>
     
    1615
    1716<pre>
    18 $details
     17${details}
    1918</pre>
    20 #end def
  • package_branches/invirt-web/cherrypy-rebased/code/view.py

    r2690 r2693  
    1 import os
     1import os, sys
    22
    33import cherrypy
     
    66import simplejson
    77import datetime, decimal
     8from StringIO import StringIO
    89from invirt.config import structs as config
    910from webcommon import State
     
    2829    def __init__(self):
    2930        self.lookups = {}
    30    
    31     def __call__(self, filename, directories, module_directory=None,
    32                  collection_size=-1, content_type='text/html; charset=utf-8',
    33                  imports=[]):
     31
     32    def get_lookup(self, directories, module_directory=None,
     33                     collection_size=-1, imports=[], **kwargs):
    3434        # Find the appropriate template lookup.
    3535        key = (tuple(directories), module_directory)
     
    4646                                    )
    4747            self.lookups[key] = lookup
    48         cherrypy.request.lookup = lookup
     48        return lookup
     49
     50    def __call__(self, filename, directories, module_directory=None,
     51                 collection_size=-1, content_type='text/html; charset=utf-8',
     52                 imports=[]):
     53        cherrypy.request.lookup = lookup = self.get_lookup(directories, module_directory,
     54                                                           collection_size, imports)
    4955       
    5056        # Replace the current handler.
     
    5460main = MakoLoader()
    5561cherrypy.tools.mako = cherrypy.Tool('on_start_resource', main)
     62
     63def revertStandardError():
     64    """Move stderr to stdout, and return the contents of the old stderr."""
     65    errio = sys.stderr
     66    if not isinstance(errio, StringIO):
     67        return ''
     68    sys.stderr = sys.stdout
     69    errio.seek(0)
     70    return errio.read()
     71
     72def catchStderr():
     73    old_handler = cherrypy.request.handler
     74    def wrapper(*args, **kwargs):
     75        sys.stderr = StringIO()
     76        ret = old_handler(*args, **kwargs)
     77        e = revertStandardError()
     78        if e:
     79            if isinstance(ret, dict):
     80                ret["error_text"] = e
     81        return ret
     82    if old_handler:
     83        cherrypy.request.handler = wrapper
     84
     85cherrypy.tools.catch_stderr = cherrypy.Tool('before_handler', catchStderr)
    5686
    5787class JSONEncoder(simplejson.JSONEncoder):
  • package_branches/invirt-web/cherrypy-rebased/debian/changelog

    r2658 r2693  
    33  * Depend on python-cherrypy3 and python-mako in preparation of migrating the
    44    web site.
    5 
    6  -- Quentin Smith <quentin@mit.edu>  Sat, 02 May 2009 22:32:53 -0400
     5  * Remove dependency on python-cheetah
     6
     7 -- Quentin Smith <quentin@mit.edu>  Mon, 28 Sep 2009 01:26:06 -0400
    78
    89invirt-web (0.0.24) unstable; urgency=low
  • package_branches/invirt-web/cherrypy-rebased/debian/control

    r2658 r2693  
    1717 debathena-ssl-certificates,
    1818# python libraries
    19  python-flup, python-cheetah, python-simplejson,
     19 python-flup, python-simplejson,
    2020 python-dns, python-dnspython, python-cherrypy3,
    2121 python-mako,
Note: See TracChangeset for help on using the changeset viewer.