Ian Bicking avatar Ian Bicking committed 66838d0

Improve errors when fetching an error page: http://trac.pythonpaste.org/pythonpaste/ticket/123

Comments (0)

Files changed (4)

 * :mod:`paste.auth.auth_tkt` will URL-quote usernames, avoiding some
   errors with usernames with ``!`` in them.
 
+* Improve handling of errors in fetching error pages in
+  :mod:`paste.errordocument`.
+
 1.7.4
 -----
 

paste/errordocument.py

 
 import warnings
 from urlparse import urlparse
-from paste.recursive import ForwardRequestException, RecursiveMiddleware
+from paste.recursive import ForwardRequestException, RecursiveMiddleware, RecursionLoop
 from paste.util import converters
 from paste.response import replace_header
 
         else:
             environ['QUERY_STRING'] = ''
         #raise Exception(self.url, self.status)
-        return self.app(environ, keep_status_start_response)
+        try:
+            return self.app(environ, keep_status_start_response)
+        except RecursionLoop, e:
+            environ['wsgi.errors'].write('Recursion error getting error page: %s\n' % e)
+            keep_status_start_response('500 Server Error', [('Content-type', 'text/plain')])
+            return ['Error: %s.  (Error page could not be fetched)'
+                    % self.status]
+
 
 class StatusBasedForward(object):
     """

paste/recursive.py

 __all__ = ['RecursiveMiddleware']
 __pudge_all__ =  ['RecursiveMiddleware', 'ForwardRequestException']
 
+class RecursionLoop(AssertionError):
+    # Subclasses AssertionError for legacy reasons
+    """Raised when a recursion enters into a loop"""
+
 class CheckForRecursionMiddleware(object):
     def __init__(self, app, env):
         self.app = app
         self.env = env
-        
+
     def __call__(self, environ, start_response):
         path_info = environ.get('PATH_INFO','')
         if path_info in self.env.get(
             'paste.recursive.old_path_info', []):
-            raise AssertionError(
+            raise RecursionLoop(
                 "Forwarding loop detected; %r visited twice (internal "
                 "redirect path: %s)"
                 % (path_info, self.env['paste.recursive.old_path_info']))
 
     def __call__(self, environ, start_response):
         environ['paste.recursive.forward'] = Forwarder(
-            self.application, 
-            environ, 
+            self.application,
+            environ,
             start_response)
         environ['paste.recursive.include'] = Includer(
-            self.application, 
-            environ, 
+            self.application,
+            environ,
             start_response)
         environ['paste.recursive.include_app_iter'] = IncluderAppIter(
             self.application,
 class ForwardRequestException(Exception):
     """
     Used to signal that a request should be forwarded to a different location.
-    
+
     ``url``
-        The URL to forward to starting with a ``/`` and relative to 
-        ``RecursiveMiddleware``. URL fragments can also contain query strings 
+        The URL to forward to starting with a ``/`` and relative to
+        ``RecursiveMiddleware``. URL fragments can also contain query strings
         so ``/error?code=404`` would be a valid URL fragment.
-    
+
     ``environ``
-        An altertative WSGI environment dictionary to use for the forwarded 
+        An altertative WSGI environment dictionary to use for the forwarded
         request. If specified is used *instead* of the ``url_fragment``
-     
+
     ``factory``
-        If specifed ``factory`` is used instead of ``url`` or ``environ``. 
-        ``factory`` is a callable that takes a WSGI application object 
+        If specifed ``factory`` is used instead of ``url`` or ``environ``.
+        ``factory`` is a callable that takes a WSGI application object
         as the first argument and returns an initialised WSGI middleware
         which can alter the forwarded response.
 
     Basic usage (must have ``RecursiveMiddleware`` present) :
-    
+
     .. code-block:: python
-    
+
         from paste.recursive import ForwardRequestException
         def app(environ, start_response):
             if environ['PATH_INFO'] == '/hello':
                 return ['Page not found']
             else:
                 raise ForwardRequestException('/error')
-                
+
         from paste.recursive import RecursiveMiddleware
         app = RecursiveMiddleware(app)
-        
-    If you ran this application and visited ``/hello`` you would get a 
-    ``Hello World!`` message. If you ran the application and visited 
+
+    If you ran this application and visited ``/hello`` you would get a
+    ``Hello World!`` message. If you ran the application and visited
     ``/not_found`` a ``ForwardRequestException`` would be raised and the caught
-    by the ``RecursiveMiddleware``. The ``RecursiveMiddleware`` would then 
-    return the headers and response from the ``/error`` URL but would display 
+    by the ``RecursiveMiddleware``. The ``RecursiveMiddleware`` would then
+    return the headers and response from the ``/error`` URL but would display
     a ``404 Not found`` status message.
-    
-    You could also specify an ``environ`` dictionary instead of a url. Using 
+
+    You could also specify an ``environ`` dictionary instead of a url. Using
     the same example as before:
-    
+
     .. code-block:: python
-    
+
         def app(environ, start_response):
             ... same as previous example ...
             else:
                 new_environ = environ.copy()
                 new_environ['PATH_INFO'] = '/error'
                 raise ForwardRequestException(environ=new_environ)
-                
+
     Finally, if you want complete control over every aspect of the forward you
-    can specify a middleware factory. For example to keep the old status code 
+    can specify a middleware factory. For example to keep the old status code
     but use the headers and resposne body from the forwarded response you might
     do this:
-    
+
     .. code-block:: python
 
         from paste.recursive import ForwardRequestException
     """
 
     def __init__(
-        self, 
-        url=None, 
-        environ={}, 
-        factory=None, 
+        self,
+        url=None,
+        environ={},
+        factory=None,
         path_info=None):
         # Check no incompatible options have been chosen
         if factory and url:
             else:
                 raise TypeError('You cannot use url and path_info in ForwardRequestException')
             self.path_info = path_info
-    
+
         # If the url can be treated as a path_info do that
-        if url and not '?' in str(url): 
+        if url and not '?' in str(url):
             self.path_info = url
-            
+
         # Base middleware
         class ForwardRequestExceptionMiddleware(object):
             def __init__(self, app):
                 self.app = app
-                            
+
         # Otherwise construct the appropriate middleware factory
         if hasattr(self, 'path_info'):
             p = self.path_info
     It must not be called after and headers have been returned.
     It returns an iterator that must be returned back up the call
     stack, so it must be used like:
-    
+
     .. code-block:: python
 
         return environ['paste.recursive.forward'](path)
             "ForwardRequestException",
             DeprecationWarning, 2)
         return self.application(environ, self.start_response)
-    
+
 
 class Includer(Recursive):
 
     overwriting any values in the `extra_environ` dictionary.
     Returns an IncludeResponse object.
     """
-    
+
     def activate(self, environ):
         response = IncludedResponse()
         def start_response(status, headers, exc_info=None):
             "Tried to close twice")
         if hasattr(self.app_iter, 'close'):
             self.app_iter.close()
-            
+
     def write(self, s):
         self.accumulated.append
 

tests/test_errordocument.py

     assert res.header('content-type') == 'text/plain'
     assert res.full_status == '200 OK'
     assert 'requested page returned' in res
-    
+
 def error_docs_app(environ, start_response):
     if environ['PATH_INFO'] == '/not_found':
         start_response("404 Not found", [('Content-type', 'text/plain')])
         return ['Page not found']
     else:
         return simple_app(environ, start_response)
-    
+
 def test_error_docs_app():
     app = TestApp(error_docs_app)
     res = app.get('')
     assert res.full_status == '404 Not found'
     assert 'Not found' in res
 
-def test_forward():    
+def test_forward():
     app = forward(error_docs_app, codes={404:'/error'})
     app = TestApp(RecursiveMiddleware(app))
     res = app.get('')
     assert res.full_status == '404 Not found'
     # Note changed response
     assert 'Page not found' in res
-    
+
 def auth_required_app(environ, start_response):
     start_response('401 Unauthorized', [('content-type', 'text/plain'), ('www-authenticate', 'Basic realm="Foo"')])
     return ['Sign in!']
-    
+
 def auth_docs_app(environ, start_response):
     if environ['PATH_INFO'] == '/auth':
         return auth_required_app(environ, start_response)
     assert res.header('content-type') == 'text/html'
     assert res.header('www-authenticate') == 'Basic realm="Foo"'
     assert res.body == '<html>Login!</html>'
+
+def test_bad_error():
+    def app(environ, start_response):
+        start_response('404 Not Found', [('content-type', 'text/plain')])
+        return ['not found']
+    app = forward(app, {404: '/404.html'})
+    app = TestApp(app)
+    resp = app.get('/test', expect_errors=True)
+    print resp
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.