summaryrefslogtreecommitdiff
path: root/flup/middleware/error.py
diff options
context:
space:
mode:
Diffstat (limited to 'flup/middleware/error.py')
-rw-r--r--flup/middleware/error.py352
1 files changed, 0 insertions, 352 deletions
diff --git a/flup/middleware/error.py b/flup/middleware/error.py
deleted file mode 100644
index 637e842..0000000
--- a/flup/middleware/error.py
+++ /dev/null
@@ -1,352 +0,0 @@
-# Copyright (c) 2005 Allan Saddi <allan@saddi.com>
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
-#
-# $Id$
-
-__author__ = 'Allan Saddi <allan@saddi.com>'
-__version__ = '$Revision$'
-
-import sys
-import os
-import traceback
-import time
-from email.Message import Message
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEText import MIMEText
-import smtplib
-
-try:
- import thread
-except ImportError:
- import dummy_thread as thread
-
-__all__ = ['ErrorMiddleware']
-
-def _wrapIterator(appIter, errorMiddleware, environ, start_response):
- """
- Wrapper around the application's iterator which catches any unhandled
- exceptions. Forwards close() and __len__ to the application iterator,
- if necessary.
- """
- class metaIterWrapper(type):
- def __init__(cls, name, bases, clsdict):
- super(metaIterWrapper, cls).__init__(name, bases, clsdict)
- if hasattr(appIter, '__len__'):
- cls.__len__ = appIter.__len__
-
- class iterWrapper(object):
- __metaclass__ = metaIterWrapper
- def __init__(self):
- self._next = iter(appIter).next
- if hasattr(appIter, 'close'):
- self.close = appIter.close
-
- def __iter__(self):
- return self
-
- def next(self):
- try:
- return self._next()
- except StopIteration:
- raise
- except:
- errorMiddleware.exceptionHandler(environ)
-
- # I'm not sure I like this next part.
- try:
- errorIter = errorMiddleware.displayErrorPage(environ,
- start_response)
- except:
- # Headers already sent, what can be done?
- raise
- else:
- # The exception occurred early enough for start_response()
- # to succeed. Swap iterators!
- self._next = iter(errorIter).next
- return self._next()
-
- return iterWrapper()
-
-class ErrorMiddleware(object):
- """
- Middleware that catches any unhandled exceptions from the application.
- Displays a (static) error page to the user while emailing details about
- the exception to an administrator.
- """
- def __init__(self, application, adminAddress,
- fromAddress='wsgiapp',
- smtpHost='localhost',
-
- applicationName=None,
-
- errorPageMimeType='text/html',
- errorPage=None,
- errorPageFile='error.html',
-
- emailInterval=15,
- intervalCheckFile='errorEmailCheck',
-
- debug=False):
- """
- Explanation of parameters:
-
- application - WSGI application.
-
- adminAddress - Email address of administrator.
- fromAddress - Email address that the error email should appear to
- originate from. By default 'wsgiapp@hostname.of.server'.
- smtpHost - SMTP email server, through which to send the email.
-
- applicationName - Name of your WSGI application, to help differentiate
- it from other applications in email. By default, this is the Python
- name of the application object. (You should explicitly set this
- if you use other middleware components, otherwise the name
- deduced will probably be that of a middleware component.)
-
- errorPageMimeType - MIME type of the static error page. 'text/html'
- by default.
- errorPage - String representing the body of the static error page.
- If None (the default), errorPageFile must point to an existing file.
- errorPageFile - File from which to take the static error page (may
- be relative to current directory or an absolute filename).
-
- emailInterval - Minimum number of minutes between error mailings,
- to prevent the administrator's mailbox from filling up.
- intervalCheckFile - When running in one-shot mode (as determined by
- the 'wsgi.run_once' environment variable), this file is used to
- keep track of the last time an email was sent. May be relative
- (to the current directory) or an absolute filename.
-
- debug - If True, will attempt to display the traceback as a webpage.
- No email is sent. If False (the default), the static error page is
- displayed and the error email is sent, if necessary.
- """
- self._application = application
-
- self._adminAddress = adminAddress
- self._fromAddress = fromAddress
- self._smtpHost = smtpHost
-
- # Set up a generic application name if not specified.
- if applicationName is None:
- applicationName = []
- if application.__module__ != '__main__':
- applicationName.append('%s.' % application.__module__)
- applicationName.append(application.__name__)
- applicationName = ''.join(applicationName)
- self._applicationName = applicationName
-
- self._errorPageMimeType = errorPageMimeType
- # If errorPage was unspecified, set it from the static file
- # specified by errorPageFile.
- if errorPage is None:
- f = open(errorPageFile)
- errorPage = f.read()
- f.close
- self._errorPage = errorPage
-
- self._emailInterval = emailInterval * 60
- self._lastEmailTime = 0
- self._intervalCheckFile = intervalCheckFile
-
- # Set up displayErrorPage appropriately.
- self._debug = debug
- if debug:
- self.displayErrorPage = self._displayDebugPage
- else:
- self.displayErrorPage = self._displayErrorPage
-
- # Lock for _lastEmailTime
- self._lock = thread.allocate_lock()
-
- def _displayErrorPage(self, environ, start_response):
- """
- Displays the static error page. May be overridden. (Maybe you'd
- rather redirect or something?) This is basically a mini-WSGI
- application, except that start_response() is called with the third
- argument.
-
- Really, there's nothing keeping you from overriding this method
- and displaying a dynamic error page. But I thought it might be safer
- to display a static page. :)
- """
- start_response('200 OK', [('Content-Type', self._errorPageMimeType),
- ('Content-Length',
- str(len(self._errorPage)))],
- sys.exc_info())
- return [self._errorPage]
-
- def _displayDebugPage(self, environ, start_response):
- """
- When debugging, display an informative traceback of the exception.
- """
- import cgitb
- result = [cgitb.html(sys.exc_info())]
- start_response('200 OK', [('Content-Type', 'text/html'),
- ('Content-Length', str(len(result[0])))],
- sys.exc_info())
- return result
-
- def _generateHTMLErrorEmail(self):
- """
- Generates the HTML version of the error email. Must return a string.
- """
- import cgitb
- return cgitb.html(sys.exc_info())
-
- def _generatePlainErrorEmail(self):
- """
- Generates the plain-text version of the error email. Must return a
- string.
- """
- import cgitb
- return cgitb.text(sys.exc_info())
-
- def _generateErrorEmail(self):
- """
- Generates the error email. Must return an instance of email.Message
- or subclass.
-
- This implementation generates a MIME multipart/alternative email with
- an HTML description of the error and a simpler plain-text alternative
- of the traceback.
- """
- msg = MIMEMultipart('alternative')
- msg.attach(MIMEText(self._generatePlainErrorEmail()))
- msg.attach(MIMEText(self._generateHTMLErrorEmail(), 'html'))
- return msg
-
- def _sendErrorEmail(self, environ):
- """
- Sends the error email as generated by _generateErrorEmail(). If
- anything goes wrong sending the email, the exception is caught
- and reported to wsgi.errors. I don't think there's really much else
- that can be done in that case.
- """
- msg = self._generateErrorEmail()
-
- msg['From'] = self._fromAddress
- msg['To'] = self._adminAddress
- msg['Subject'] = '%s: unhandled exception' % self._applicationName
-
- try:
- server = smtplib.SMTP(self._smtpHost)
- server.sendmail(self._fromAddress, self._adminAddress,
- msg.as_string())
- server.quit()
- except Exception, e:
- stderr = environ['wsgi.errors']
- stderr.write('%s: Failed to send error email: %r %s\n' %
- (self.__class__.__name__, e, e))
- stderr.flush()
-
- def _shouldSendEmail(self, environ):
- """
- Returns True if an email should be sent. The last time an email was
- sent is tracked by either an instance variable (if oneShot is False),
- or the mtime of a file on the filesystem (if oneShot is True).
- """
- if self._debug or self._adminAddress is None:
- # Never send email when debugging or when there's no admin
- # address.
- return False
-
- now = time.time()
- if not environ['wsgi.run_once']:
- self._lock.acquire()
- ret = (self._lastEmailTime + self._emailInterval) < now
- if ret:
- self._lastEmailTime = now
- self._lock.release()
- else:
- # The following should be protected, but do I *really* want
- # to get into the mess of using filesystem and file-based locks?
- # At worse, multiple emails get sent.
- ret = True
-
- try:
- mtime = os.path.getmtime(self._intervalCheckFile)
- except:
- # Assume file doesn't exist, which is OK. Send email
- # unconditionally.
- pass
- else:
- if (mtime + self._emailInterval) >= now:
- ret = False
-
- if ret:
- # NB: If _intervalCheckFile cannot be created or written to
- # for whatever reason, you will *always* get an error email.
- try:
- open(self._intervalCheckFile, 'w').close()
- except:
- # Probably a good idea to report failure.
- stderr = environ['wsgi.errors']
- stderr.write('%s: Error writing intervalCheckFile %r\n'
- % (self.__class__.__name__,
- self._intervalCheckFile))
- stderr.flush()
- return ret
-
- def exceptionHandler(self, environ):
- """
- Common handling of exceptions.
- """
- # Unconditionally report to wsgi.errors.
- stderr = environ['wsgi.errors']
- traceback.print_exc(file=stderr)
- stderr.flush()
-
- # Send error email, if needed.
- if self._shouldSendEmail(environ):
- self._sendErrorEmail(environ)
-
- def __call__(self, environ, start_response):
- """
- WSGI application interface. Simply wraps the call to the application
- with a try ... except. All the fancy stuff happens in the except
- clause.
- """
- try:
- return _wrapIterator(self._application(environ, start_response),
- self, environ, start_response)
- except:
- # Report the exception.
- self.exceptionHandler(environ)
-
- # Display static error page.
- return self.displayErrorPage(environ, start_response)
-
-if __name__ == '__main__':
- def myapp(environ, start_response):
- start_response('200 OK', [('Content-Type', 'text/plain')])
- raise RuntimeError, "I'm broken!"
- return ['Hello World!\n']
-
- # Note - email address is taken from sys.argv[1]. I'm not leaving
- # my email address here. ;)
- app = ErrorMiddleware(myapp, sys.argv[1])
-
- from ajp import WSGIServer
- WSGIServer(app).run()