Very useful message -- Hah!

Barry A. Warsaw bwarsaw at cnri.reston.va.us
Fri Dec 3 22:54:25 EST 1999


>>>>> "DG" == Dan Grassi <Dan at Grassi.com> writes:

    DG> It seems that by design python, by design, is not to be used
    DG> as a cgi scripting language -- otherwise why would it produce
    DG> this very meaningful message at the slightest syntax error:

Sorry, I'll have to strongly disagree.  Python is an excellent
language for writing CGI applications.  Existence proof: Mailman
<www.list.org>.  You just have to be smart about reporting errors
without propagating a non-zero exit status to the Web server.  Mailman
comes with a script called `driver' which I think does this very
well.  Since it might be useful to you, I'll attach the file below.

You may have to adapt it for your purposes.  You may not have all the
modules this depends on (get them from the Mailman distro).  It's also
long because it's well commented and pretty paranoid.

Note that this shows a good use of bare excepts!

-Barry

-------------------- snip snip --------------------
#! /usr/bin/env python
#
# Copyright (C) 1998 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software 
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

# This better succeed.  If this fails, Python is royally screwed so we might
# as well let the Web server give us a fatal and obtrusive error.
import sys

# From here on we are as bulletproof as possible!



# This function is useful for debugging.  When an error occurs, this attaches
# the file name to the exception string and re-raises.  This will be
# unnecessary in Python 1.5.2, which also does sensible things to most os
# module functions.

##realopen = open
##def open(filename, mode='r', bufsize=-1, realopen=realopen):
##    from Mailman.Utils import reraise
##    try:
##        return realopen(filename, mode, bufsize)
##    except IOError, e:
##        strerror = e.strerror + ': ' + filename
##        e.strerror = strerror
##        e.filename = filename
##        e.args = (e.args[0], strerror)
##        reraise(e)

##import __builtin__
##__builtin__.__dict__['open'] = open



# This standard driver script is used to run CGI programs, wrapped in code
# that catches errors, and displays them as HTML.  This guarantees that
# (almost) any problems in the Mailman software doesn't result in a Web server
# error.  It is much more helpful to generate and show a traceback, which the
# user could send to the administrator, than to display a server error and
# have to trudge through server logs.

# Note: this isn't 100% perfect!  Here are some things that can go wrong that
# are not caught and reported as traceback-containing HTML:
#
# - This file could contain a syntax error.  In that case, you would indeed
#   get a Web server error since this file wouldn't even compile, and there's
#   no way to catch that.
#
# - The sys module could be royally screwed, probably we couldn't import it.
#   Both those would indicate serious problems in the Python installation.
#   These won't generate Web server errors, but neither will they give
#   meaningful tracebacks.
#
# I consider these pretty unlikely.



def run_main():
    try:
        # These will ensure that even if something between now and the
        # creation of the real logger below fails, we can still get
        # *something* meaningful
        logger = None
        # insert the relative path to the parent of the Mailman package
        # directory, so we can pick up the Utils module
        import os
        # sys gets imported at module level below
        sys.path.insert(0, os.pardir)
        # map stderr to a logger, if possible
        from Mailman.Logging.StampedLogger import StampedLogger
        logger = StampedLogger('error',
                               label='admin',
                               manual_reprime=1,
                               nofail=0,
                               immediate=1)
        # pre-load the `cgi' module.  we do this because we're distributing a
        # slightly different version than the standard Python module.  it's
        # essentially Python 1.5.2's module, with an experimental patch to
        # handle clients that give bogus or non-existant content-type headers.
        #
        # we assign sys.modules['cgi'] to this special cgi module because we
        # don't want to have to rewrite all the Mailman.Cgi modules to get the
        # special one.
        import Mailman.pythonlib.cgi
        sys.modules['cgi'] = Mailman.pythonlib.cgi
        # The name of the module to run is passed in argv[1].  What we
        # actually do is import the module named by argv[1] that lives in the
        # Mailman.Cgi package.  That module must have a main() function, which
        # we dig out and call.
        #
        scriptname = sys.argv[1]
        # See the reference manual for why we have to do things this way.
        # Note that importing should have no side-effects!
        pkg = __import__('Mailman.Cgi', globals(), locals(), [scriptname])
        module = getattr(pkg, scriptname)
        main = getattr(module, 'main')
        try:
            main()
        except SystemExit:
            # this is a valid way for the function to exit
            pass
    except:
        print_traceback(logger)
        print_environment(logger)



# We are printing error reporting to two places.  One will always be stdout
# and the other will always be the log file.  It is assumed that stdout is an
# HTML sink and the log file is a plain text sink.

def print_traceback(logfp=None):
    if logfp is None:
        logfp = sys.__stderr__

    try:
        import traceback
    except ImportError:
        traceback = None
    try:
        from Mailman.mm_cfg import VERSION
    except ImportError:
        VERSION = '<undetermined>'

    # write to the log file first
    logfp.write('@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n')
    logfp.write('[----- Mailman Version: %s -----]\n' % VERSION)
    logfp.write('[----- Traceback ------]\n')
    if traceback:
        traceback.print_exc(file=logfp)
    else:
        logfp.write('[failed to import module traceback]\n')
        logfp.write('[exc: %s, var: %s]\n' % sys.exc_info()[0:2])

    # print to the HTML sink
    print """\
Content-type: text/html

<head><title>Bug in Mailman version %(VERSION)s</title></head>
<body><h2>Bug in Mailman version %(VERSION)s</h2>
<p><h3>We're sorry, we hit a bug!</h3>

<p>If you would like to help us identify the problem, please
email a copy of this page to the webmaster for this site with
a description of what happened.  Thanks!

<h4>Traceback:</h4>
<p><pre>
""" % locals()
    if traceback:
        traceback.print_exc(file=sys.stdout)
    else:
        print '[failed to import module traceback]'
        print '[exc: %s, var: %s]' % sys.exc_info()[0:2]
    print '\n\n</pre></body>'



def print_environment(logfp=None):
    if logfp is None:
        logfp = sys.__stderr__

    try:
        import os
    except ImportError:
        os = None

    # write to the log file first
    logfp.write('[----- Environment Variables -----]\n')
    if os:
        for k, v in os.environ.items():
            logfp.write('\t%s: %s\n' % (k, v))
    else:
        logfp.write('[failed to import module os]\n')

    # write to the HTML sink
    if os:
        print '''\
<p><hr><h4>Environment variables:</h4>

<p><table>
<tr><td><strong>Variable</strong></td>
<td><strong>Value</strong></td></tr>
'''
        for k, v in os.environ.items():
            print '<tr><td>', k, '</td><td>', v, '</td></tr>'
        print '</table>'
    else:
        print '<p><hr>[failed to import module os]'



try:
    # Python 1.5 doesn't have these by default.  Let's make our lives easy
    if not hasattr(sys, '__stderr__'):
        sys.__stderr__ = sys.stderr
    if not hasattr(sys, '__stdout__'):
        sys.__stdout__ = sys.stdout

    run_main()
except:
    # Some exception percolated all the way back up to the top.  This
    # generally shouldn't happen because the run_main() call is similarly
    # wrapped, but just in case, we'll give it one last ditch effort to report 
    # problems to *somebody*.  Most likely this will end up in the Web server
    # log file.
    try:
        print_traceback()
        print_environment()
    except:
        # Nope, we're quite screwed
        print """\
Content-type: text/html

<p><h3>We're sorry, we hit a bug!</h3>

Mailman experienced a very low level failure and could not even generate a
useful traceback for you.  Please report this to the Mailman administrator at
this site.
"""
        sys.__stderr__.write('[Mailman: low level unrecoverable exception]\n')




More information about the Python-list mailing list