Changeset 2485


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

Full error handling

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

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()
  • package_branches/invirt-web/cherrypy/code/templates/error.mako

    r2484 r2485  
    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/code/view.py

    r2482 r2485  
    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/debian/changelog

    r2384 r2485  
    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.21) unstable; urgency=low
  • package_branches/invirt-web/cherrypy/debian/control

    r2384 r2485  
    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.