Ignore:
Timestamp:
Sep 28, 2009, 3:04:33 AM (15 years ago)
Author:
quentin
Message:

Full error handling

File:
1 edited

Legend:

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

    r2484 r2485  
    1111import random
    1212import sha
    13 import simplejson
    1413import sys
    1514import threading
     
    1817import socket
    1918import cherrypy
     19from cherrypy import _cperror
    2020from StringIO import StringIO
    21 
    22 def revertStandardError():
    23     """Move stderr to stdout, and return the contents of the old stderr."""
    24     errio = sys.stderr
    25     if not isinstance(errio, StringIO):
    26         return ''
    27     sys.stderr = sys.stdout
    28     errio.seek(0)
    29     return errio.read()
    3021
    3122def printError():
     
    3829    atexit.register(printError)
    3930
    40 import templates
    41 from Cheetah.Template import Template
    4231import validation
    4332import cache_acls
     
    5140import invirt.remctl
    5241
    53 from view import View
     42from view import View, revertStandardError
    5443import ajaxterm
    5544
     
    6554        connect()
    6655        self._cp_config['tools.require_login.on'] = True
     56        self._cp_config['tools.catch_stderr.on'] = True
    6757        self._cp_config['tools.mako.imports'] = ['from invirt.config import structs as config',
    6858                                                 'from invirt import database']
     59        self._cp_config['request.error_response'] = self.handle_error
     60
     61    @cherrypy.expose
     62    @cherrypy.tools.mako(filename="/invalid.mako")
     63    def invalidInput(self):
     64        """Print an error page when an InvalidInput exception occurs"""
     65        err = cherrypy.request.prev.params["err"]
     66        emsg = cherrypy.request.prev.params["emsg"]
     67        d = dict(err_field=err.err_field,
     68                 err_value=str(err.err_value), stderr=emsg,
     69                 errorMessage=str(err))
     70        return d
     71
     72    @cherrypy.expose
     73    @cherrypy.tools.mako(filename="/error.mako")
     74    def error(self):
     75        #op, username, fields, err, emsg, traceback):
     76        """Print an error page when an exception occurs"""
     77        op = cherrypy.request.prev.path_info
     78        username = cherrypy.request.login
     79        err = cherrypy.request.prev.params["err"]
     80        emsg = cherrypy.request.prev.params["emsg"]
     81        traceback = cherrypy.request.prev.params["traceback"]
     82        d = dict(op = op, user=username, fields=cherrypy.request.prev.params,
     83                 errorMessage=str(err), stderr=emsg, traceback=traceback)
     84        error_raw = cherrypy.tools.mako.callable.get_lookup(**cherrypy.tools.mako._merged_args()).get_template("/error_raw.mako")
     85        details = error_raw.render(**d)
     86        exclude = config.web.errormail_exclude
     87        if username not in exclude and '*' not in exclude:
     88            send_error_mail('xvm error on %s for %s: %s' % (op, cherrypy.request.login, err),
     89                            details)
     90        d['details'] = details
     91        return d
    6992
    7093    def __getattr__(self, name):
     
    77100        else:
    78101            return super(InvirtWeb, self).__getattr__(name)
     102
     103    def handle_error(self):
     104        err = sys.exc_info()[1]
     105        if isinstance(err, InvalidInput):
     106            e = revertStandardError()
     107            cherrypy.request.params['err'] = err
     108            cherrypy.request.params['emsg'] = e
     109            raise cherrypy.InternalRedirect('/invalidInput')
     110        if not cherrypy.request.prev or 'err' not in cherrypy.request.prev.params:
     111            e = revertStandardError()
     112            cherrypy.request.params['err'] = err
     113            cherrypy.request.params['emsg'] = e
     114            cherrypy.request.params['traceback'] = _cperror.format_exc()
     115            raise cherrypy.InternalRedirect('/error')
     116        # fall back to cherrypy default error page
     117        cherrypy.HTTPError(500).set_response()
    79118
    80119    @cherrypy.expose
     
    207246    def errortest(self):
    208247        """Throw an error, to test the error-tracing mechanisms."""
     248        print >>sys.stderr, "look ma, it's a stderr"
    209249        raise RuntimeError("test of the emergency broadcast system")
    210250
     
    309349                    raise
    310350                print >> sys.stderr, err
    311                 result = err
     351                result = str(err)
    312352            else:
    313353                result = 'Success!'
     
    373413    machine = MachineView()
    374414
    375 def pathSplit(path):
    376     if path.startswith('/'):
    377         path = path[1:]
    378     i = path.find('/')
    379     if i == -1:
    380         i = len(path)
    381     return path[:i], path[i:]
    382 
    383415class Checkpoint:
    384416    def __init__(self):
     
    395427
    396428checkpoint = Checkpoint()
    397 
    398 def makeErrorPre(old, addition):
    399     if addition is None:
    400         return
    401     if old:
    402         return old[:-6]  + '\n----\n' + str(addition) + '</pre>'
    403     else:
    404         return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
    405 
    406 Template.database = database
    407 Template.config = config
    408 Template.err = None
    409 
    410 class JsonDict:
    411     """Class to store a dictionary that will be converted to JSON"""
    412     def __init__(self, **kws):
    413         self.data = kws
    414         if 'err' in kws:
    415             err = kws['err']
    416             del kws['err']
    417             self.addError(err)
    418 
    419     def __str__(self):
    420         return simplejson.dumps(self.data)
    421 
    422     def addError(self, text):
    423         """Add stderr text to be displayed on the website."""
    424         self.data['err'] = \
    425             makeErrorPre(self.data.get('err'), text)
    426429
    427430class Defaults:
     
    443446        for key in kws:
    444447            setattr(self, key, kws[key])
    445 
    446 
    447 
    448 DEFAULT_HEADERS = {'Content-Type': 'text/html'}
    449 
    450 def invalidInput(op, username, fields, err, emsg):
    451     """Print an error page when an InvalidInput exception occurs"""
    452     d = dict(op=op, user=username, err_field=err.err_field,
    453              err_value=str(err.err_value), stderr=emsg,
    454              errorMessage=str(err))
    455     return templates.invalid(searchList=[d])
    456448
    457449def hasVnc(status):
     
    623615    return dict(machine=machine)
    624616
    625 
    626 def badOperation(u, s, p, e):
    627     """Function called when accessing an unknown URI."""
    628     return ({'Status': '404 Not Found'}, 'Invalid operation.')
    629 
    630617def infoDict(username, state, machine):
    631618    """Get the variables used by info.tmpl."""
     
    716703    return d
    717704
    718 mapping = dict()
    719 
    720 def printHeaders(headers):
    721     """Print a dictionary as HTTP headers."""
    722     for key, value in headers.iteritems():
    723         print '%s: %s' % (key, value)
    724     print
    725 
    726705def send_error_mail(subject, body):
    727706    import subprocess
     
    740719    p.wait()
    741720
    742 def show_error(op, username, fields, err, emsg, traceback):
    743     """Print an error page when an exception occurs"""
    744     d = dict(op=op, user=username, fields=fields,
    745              errorMessage=str(err), stderr=emsg, traceback=traceback)
    746     details = templates.error_raw(searchList=[d])
    747     exclude = config.web.errormail_exclude
    748     if username not in exclude and '*' not in exclude:
    749         send_error_mail('xvm error on %s for %s: %s' % (op, username, err),
    750                         details)
    751     d['details'] = details
    752     return templates.error(searchList=[d])
    753 
    754 def handler(username, state, path, fields):
    755     operation, path = pathSplit(path)
    756     if not operation:
    757         operation = 'list'
    758     print 'Starting', operation
    759     fun = mapping.get(operation, badOperation)
    760     return fun(username, state, path, fields)
    761 
    762 class App:
    763     def __init__(self, environ, start_response):
    764         self.environ = environ
    765         self.start = start_response
    766 
    767         self.username = getUser(environ)
    768         self.state = State(self.username)
    769         self.state.environ = environ
    770 
    771         random.seed() #sigh
    772 
    773     def __iter__(self):
    774         start_time = time.time()
    775         database.clear_cache()
    776         sys.stderr = StringIO()
    777         fields = cgi.FieldStorage(fp=self.environ['wsgi.input'], environ=self.environ)
    778         operation = self.environ.get('PATH_INFO', '')
    779         if not operation:
    780             self.start("301 Moved Permanently", [('Location', './')])
    781             return
    782         if self.username is None:
    783             operation = 'unauth'
    784 
    785         try:
    786             checkpoint.checkpoint('Before')
    787             output = handler(self.username, self.state, operation, fields)
    788             checkpoint.checkpoint('After')
    789 
    790             headers = dict(DEFAULT_HEADERS)
    791             if isinstance(output, tuple):
    792                 new_headers, output = output
    793                 headers.update(new_headers)
    794             e = revertStandardError()
    795             if e:
    796                 if hasattr(output, 'addError'):
    797                     output.addError(e)
    798                 else:
    799                     # This only happens on redirects, so it'd be a pain to get
    800                     # the message to the user.  Maybe in the response is useful.
    801                     output = output + '\n\nstderr:\n' + e
    802             output_string =  str(output)
    803             checkpoint.checkpoint('output as a string')
    804         except Exception, err:
    805             if not fields.has_key('js'):
    806                 if isinstance(err, InvalidInput):
    807                     self.start('200 OK', [('Content-Type', 'text/html')])
    808                     e = revertStandardError()
    809                     yield str(invalidInput(operation, self.username, fields,
    810                                            err, e))
    811                     return
    812             import traceback
    813             self.start('500 Internal Server Error',
    814                        [('Content-Type', 'text/html')])
    815             e = revertStandardError()
    816             s = show_error(operation, self.username, fields,
    817                            err, e, traceback.format_exc())
    818             yield str(s)
    819             return
    820         status = headers.setdefault('Status', '200 OK')
    821         del headers['Status']
    822         self.start(status, headers.items())
    823         yield output_string
    824         if fields.has_key('timedebug'):
    825             yield '<pre>%s</pre>' % cgi.escape(str(checkpoint))
    826 
    827 def constructor():
    828     connect()
    829     return App
    830 
    831 def main():
    832     from flup.server.fcgi_fork import WSGIServer
    833     WSGIServer(constructor()).run()
    834 
    835 if __name__ == '__main__':
    836     main()
     721random.seed()
Note: See TracChangeset for help on using the changeset viewer.