summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/news.txt3
-rw-r--r--paste/errordocument.py11
-rw-r--r--paste/recursive.py86
-rw-r--r--tests/test_errordocument.py19
4 files changed, 71 insertions, 48 deletions
diff --git a/docs/news.txt b/docs/news.txt
index f2dd71c..38231d3 100644
--- a/docs/news.txt
+++ b/docs/news.txt
@@ -20,6 +20,9 @@ tip
* :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
-----
diff --git a/paste/errordocument.py b/paste/errordocument.py
index 4b9701a..d3d35bb 100644
--- a/paste/errordocument.py
+++ b/paste/errordocument.py
@@ -11,7 +11,7 @@ URL where the content can be displayed to the user as an error document.
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
@@ -81,7 +81,14 @@ class StatusKeeper(object):
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):
"""
diff --git a/paste/recursive.py b/paste/recursive.py
index 8df060b..8ea7038 100644
--- a/paste/recursive.py
+++ b/paste/recursive.py
@@ -29,16 +29,20 @@ import warnings
__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']))
@@ -63,12 +67,12 @@ class RecursiveMiddleware(object):
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,
@@ -86,26 +90,26 @@ class RecursiveMiddleware(object):
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':
@@ -116,34 +120,34 @@ class ForwardRequestException(Exception):
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
@@ -166,10 +170,10 @@ class ForwardRequestException(Exception):
"""
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:
@@ -195,16 +199,16 @@ class ForwardRequestException(Exception):
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
@@ -285,7 +289,7 @@ class Forwarder(Recursive):
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)
@@ -301,7 +305,7 @@ class Forwarder(Recursive):
"ForwardRequestException",
DeprecationWarning, 2)
return self.application(environ, self.start_response)
-
+
class Includer(Recursive):
@@ -310,7 +314,7 @@ 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):
@@ -391,7 +395,7 @@ class IncludedAppIterResponse(object):
"Tried to close twice")
if hasattr(self.app_iter, 'close'):
self.app_iter.close()
-
+
def write(self, s):
self.accumulated.append
diff --git a/tests/test_errordocument.py b/tests/test_errordocument.py
index a3da3b8..24f3022 100644
--- a/tests/test_errordocument.py
+++ b/tests/test_errordocument.py
@@ -16,7 +16,7 @@ def test_ok():
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')])
@@ -26,7 +26,7 @@ def error_docs_app(environ, start_response):
return ['Page not found']
else:
return simple_app(environ, start_response)
-
+
def test_error_docs_app():
app = TestApp(error_docs_app)
res = app.get('')
@@ -42,7 +42,7 @@ def test_error_docs_app():
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('')
@@ -58,11 +58,11 @@ def test_forward():
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)
@@ -81,3 +81,12 @@ def test_auth_docs_app():
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