summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cherrypy/_cperror.py19
-rw-r--r--cherrypy/lib/__init__.py13
-rw-r--r--cherrypy/lib/cptools.py5
-rw-r--r--cherrypy/lib/sessions.py3
-rw-r--r--cherrypy/test/static/404.html5
-rw-r--r--cherrypy/test/test_core.py10
-rw-r--r--cherrypy/test/test_static.py16
7 files changed, 57 insertions, 14 deletions
diff --git a/cherrypy/_cperror.py b/cherrypy/_cperror.py
index 7632ed20..bc556ab9 100644
--- a/cherrypy/_cperror.py
+++ b/cherrypy/_cperror.py
@@ -394,11 +394,11 @@ class HTTPError(CherryPyException):
tb = None
if cherrypy.serving.request.show_tracebacks:
tb = format_exc()
- response.headers['Content-Type'] = "text/html;charset=utf-8"
+
response.headers.pop('Content-Length', None)
content = self.get_error_page(self.status, traceback=tb,
- message=self._message).encode('utf-8')
+ message=self._message)
response.body = content
_be_ie_unfriendly(self.code)
@@ -494,13 +494,19 @@ def get_error_page(status, **kwargs):
# Use a custom template or callable for the error page?
pages = cherrypy.serving.request.error_page
error_page = pages.get(code) or pages.get('default')
+
+ # Default template, can be overridden below.
+ template = _HTTPErrorTemplate
if error_page:
try:
if hasattr(error_page, '__call__'):
+ # The caller function may be setting headers manually,
+ # so we delegate to it completely. We may be returning
+ # an iterator as well as a string here.
return error_page(**kwargs)
else:
- data = open(error_page, 'rb').read()
- return tonative(data) % kwargs
+ # Load the template from this path.
+ template = tonative(open(error_page, 'rb').read())
except:
e = _format_exception(*_exc_info())[-1]
m = kwargs['message']
@@ -509,7 +515,10 @@ def get_error_page(status, **kwargs):
m += "In addition, the custom error page failed:\n<br />%s" % e
kwargs['message'] = m
- return _HTTPErrorTemplate % kwargs
+ response = cherrypy.serving.response
+ response.headers['Content-Type'] = "text/html;charset=utf-8"
+ result = template % kwargs
+ return result.encode('utf-8')
_ie_friendly_error_sizes = {
diff --git a/cherrypy/lib/__init__.py b/cherrypy/lib/__init__.py
index 554b3920..2c851ec4 100644
--- a/cherrypy/lib/__init__.py
+++ b/cherrypy/lib/__init__.py
@@ -3,6 +3,19 @@
# Deprecated in CherryPy 3.2 -- remove in CherryPy 3.3
from cherrypy.lib.reprconf import unrepr, modules, attributes
+def is_iterator(obj):
+ '''Returns a boolean indicating if the object provided implements
+ the iterator protocol (i.e. like a generator). This will return
+ false for objects which iterable, but not iterators themselves.'''
+ from types import GeneratorType
+ if isinstance(obj, GeneratorType):
+ return True
+ elif not hasattr(obj, '__iter__'):
+ return False
+ else:
+ # Types which implement the protocol must return themselves when
+ # invoking 'iter' upon them.
+ return iter(obj) is obj
class file_generator(object):
diff --git a/cherrypy/lib/cptools.py b/cherrypy/lib/cptools.py
index 134c8e47..66c9c1b8 100644
--- a/cherrypy/lib/cptools.py
+++ b/cherrypy/lib/cptools.py
@@ -6,6 +6,7 @@ import re
import cherrypy
from cherrypy._cpcompat import basestring, md5, set, unicodestr
from cherrypy.lib import httputil as _httputil
+from cherrypy.lib import is_iterator
# Conditional HTTP request support #
@@ -493,12 +494,10 @@ def flatten(debug=False):
This allows cherrypy.response.body to consist of 'nested generators';
that is, a set of generators that yield generators.
"""
- import types
-
def flattener(input):
numchunks = 0
for x in input:
- if not isinstance(x, types.GeneratorType):
+ if not is_iterator(x):
numchunks += 1
yield x
else:
diff --git a/cherrypy/lib/sessions.py b/cherrypy/lib/sessions.py
index 23115c50..42c89d8c 100644
--- a/cherrypy/lib/sessions.py
+++ b/cherrypy/lib/sessions.py
@@ -101,6 +101,7 @@ from cherrypy._cpcompat import copyitems, pickle, random20, unicodestr
from cherrypy.lib import httputil
from cherrypy.lib import lockfile
from cherrypy.lib import locking
+from cherrypy.lib import is_iterator
missing = object()
@@ -778,7 +779,7 @@ def save():
else:
# If the body is not being streamed, we save the data now
# (so we can release the lock).
- if isinstance(response.body, types.GeneratorType):
+ if is_iterator(response.body):
response.collapse_body()
cherrypy.session.save()
save.failsafe = True
diff --git a/cherrypy/test/static/404.html b/cherrypy/test/static/404.html
new file mode 100644
index 00000000..01b17b09
--- /dev/null
+++ b/cherrypy/test/static/404.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ <h1>I couldn't find that thing you were looking for!</h1>
+ </body>
+</html>
diff --git a/cherrypy/test/test_core.py b/cherrypy/test/test_core.py
index 86f59191..6966d325 100644
--- a/cherrypy/test/test_core.py
+++ b/cherrypy/test/test_core.py
@@ -317,15 +317,15 @@ class CoreRequestHandlingTest(helper.CPWebCase):
# Make sure GET params are preserved.
self.getPage("/redirect?id=3")
self.assertStatus(301)
- self.assertInBody("<a href='%s/redirect/?id=3'>"
- "%s/redirect/?id=3</a>" % (self.base(), self.base()))
+ self.assertMatchesBody('<a href=([\'"])%s/redirect/[?]id=3\\1>'
+ "%s/redirect/[?]id=3</a>" % (self.base(), self.base()))
if self.prefix():
# Corner case: the "trailing slash" redirect could be tricky if
# we're using a virtual root and the URI is "/vroot" (no slash).
self.getPage("")
self.assertStatus(301)
- self.assertInBody("<a href='%s/'>%s/</a>" %
+ self.assertMatchesBody("<a href=(['\"])%s/\\1>%s/</a>" %
(self.base(), self.base()))
# Test that requests for NON-index methods WITH a trailing slash
@@ -333,8 +333,8 @@ class CoreRequestHandlingTest(helper.CPWebCase):
# Make sure GET params are preserved.
self.getPage("/redirect/by_code/?code=307")
self.assertStatus(301)
- self.assertInBody("<a href='%s/redirect/by_code?code=307'>"
- "%s/redirect/by_code?code=307</a>"
+ self.assertMatchesBody("<a href=(['\"])%s/redirect/by_code[?]code=307\\1>"
+ "%s/redirect/by_code[?]code=307</a>"
% (self.base(), self.base()))
# If the trailing_slash tool is off, CP should just continue
diff --git a/cherrypy/test/test_static.py b/cherrypy/test/test_static.py
index a64fa586..de86d0bf 100644
--- a/cherrypy/test/test_static.py
+++ b/cherrypy/test/test_static.py
@@ -80,6 +80,12 @@ class StaticTest(helper.CPWebCase):
'tools.staticdir.on': True,
'request.show_tracebacks': True,
},
+ '/404test': {
+ 'tools.staticdir.on': True,
+ 'tools.staticdir.root': curdir,
+ 'tools.staticdir.dir': 'static',
+ 'error_page.404': error_page_404,
+ }
}
rootApp = cherrypy.Application(root)
rootApp.merge(rootconf)
@@ -301,3 +307,13 @@ class StaticTest(helper.CPWebCase):
if self.body != ntob("x" * BIGFILE_SIZE):
self.fail("Body != 'x' * %d. Got %r instead (%d bytes)." %
(BIGFILE_SIZE, self.body[:50], len(body)))
+
+ def test_error_page_with_serve_file(self):
+ self.getPage("/404test/yunyeen")
+ self.assertStatus(404)
+ self.assertInBody("I couldn't find that thing")
+
+def error_page_404(status, message, traceback, version):
+ import os.path
+ return static.serve_file(os.path.join(curdir, 'static', '404.html'),
+ content_type='text/html')