diff options
-rw-r--r-- | cherrypy/_cperror.py | 19 | ||||
-rw-r--r-- | cherrypy/lib/__init__.py | 13 | ||||
-rw-r--r-- | cherrypy/lib/cptools.py | 5 | ||||
-rw-r--r-- | cherrypy/lib/sessions.py | 3 | ||||
-rw-r--r-- | cherrypy/test/static/404.html | 5 | ||||
-rw-r--r-- | cherrypy/test/test_core.py | 10 | ||||
-rw-r--r-- | cherrypy/test/test_static.py | 16 |
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') |