Robert Brewer avatar Robert Brewer committed 9213e62

Applying Nicolas Griily's patch for #800.

Comments (0)

Files changed (3)

cherrypy/_cperror.py

 
 from cgi import escape as _escape
 from sys import exc_info as _exc_info
+from traceback import format_exception as _format_exception
 from urlparse import urljoin as _urljoin
 from cherrypy.lib import http as _http
 
         kwargs['traceback'] = ''
     if kwargs.get('version') is None:
         kwargs['version'] = cherrypy.__version__
+    
     for k, v in kwargs.iteritems():
         if v is None:
             kwargs[k] = ""
         else:
             kwargs[k] = _escape(kwargs[k])
     
-    template = _HTTPErrorTemplate
-    
-    # Replace the default template with a custom one?
-    error_page_file = cherrypy.request.error_page.get(code, '')
-    if error_page_file:
+    # Use a custom template or callable for the error page?
+    pages = cherrypy.request.error_page
+    error_page = pages.get(code) or pages.get('default')
+    if error_page:
         try:
-            template = file(error_page_file, 'rb').read()
+            if callable(error_page):
+                return error_page(**kwargs)
+            else:
+                return file(error_page, 'rb').read() % kwargs
         except:
+            e = _format_exception(*_exc_info())[-1]
             m = kwargs['message']
             if m:
                 m += "<br />"
-            m += ("In addition, the custom error page "
-                  "failed:\n<br />%s" % (_exc_info()[1]))
+            m += "In addition, the custom error page failed:\n<br />%s" % e
             kwargs['message'] = m
     
-    return template % kwargs
+    return _HTTPErrorTemplate % kwargs
 
 
 _ie_friendly_error_sizes = {
              ('Content-Length', str(len(body)))],
             [body])
 
+

cherrypy/_cprequest.py

 
 def error_page_namespace(k, v):
     """Attach error pages declared in config."""
-    cherrypy.request.error_page[int(k)] = v
+    if k != 'default':
+        k = int(k)
+    cherrypy.request.error_page[k] = v
 
 
 hookpoints = ['on_start_resource', 'before_request_body',
     error_page = {}
     error_page__doc = """
     A dict of {error code: response filename or callable} pairs.
-    The named response files should be Python string-formatting templates,
-    and can expect by default to receive format values with the mapping
-    keys 'status', 'message', 'traceback', and 'version'. The set of
-    format mappings can be extended by overriding HTTPError.set_response.
     
-    If a callable is provided, it will be called with the HTTPError
-    instance as the only argument. The callable is expected to set
-    response.status, .headers and .body appropriately.
+    The error code must be an int representing a given HTTP error code,
+    or the string 'default', which will be used if no matching entry
+    is found for a given numeric code.
     
-    If no entry is given for an error code, the HTTPError's set_response
-    method will handle the error (by setting .status, .headers, and .body).
+    If a filename is provided, the file should contain a Python string-
+    formatting template, and can expect by default to receive format 
+    values with the mapping keys %(status)s, %(message)s, %(traceback)s,
+    and %(version)s. The set of format mappings can be extended by
+    overriding HTTPError.set_response.
+    
+    If a callable is provided, it will be called by default with keyword 
+    arguments 'status', 'message', 'traceback', and 'version', as for a
+    string-formatting template. The callable must return a string which
+    will be set to response.body. It may also override headers or perform
+    any other processing.
+    
+    If no entry is given for an error code, and no 'default' entry exists,
+    a default template will be used.
     """
     
     show_tracebacks = True
                     self.hooks.run('before_finalize')
                     cherrypy.response.finalize()
                 except (cherrypy.HTTPRedirect, cherrypy.HTTPError), inst:
-                    ep = self.error_page.get(inst.status, '')
-                    if ep and callable(ep):
-                        ep(inst)
-                    else:
-                        inst.set_response()
+                    inst.set_response()
                     self.stage = 'before_finalize (HTTPError)'
                     self.hooks.run('before_finalize')
                     cherrypy.response.finalize()
             self.hooks.run("after_error_response")
             cherrypy.response.finalize()
         except cherrypy.HTTPRedirect, inst:
-            ep = self.error_page.get(inst.status, '')
-            if ep and callable(ep):
-                ep(inst)
-            else:
-                inst.set_response()
+            inst.set_response()
             cherrypy.response.finalize()
 
 
             self.timed_out = True
 
 
+

cherrypy/test/test_core.py

                 yield chunk
     
     
-    def page401(e):
-        cherrypy.response.status = e.status
-        cherrypy._cperror.clean_headers(e.status)
-        cherrypy.response.body = "Well, I'm very sorry but you haven't paid!"
+    def callable_error_page(status, **kwargs):
+        return "Error %s - Well, I'm very sorry but you haven't paid!" % status
     
     
     class Error(Test):
         def custom(self, err='404'):
             raise cherrypy.HTTPError(int(err), "No, <b>really</b>, not found!")
         custom._cp_config = {'error_page.404': os.path.join(localDir, "static/index.html"),
-                             'error_page.401': page401,
+                             'error_page.401': callable_error_page,
                              }
         
+        def custom_default(self):
+            return 1 + 'a' # raise an unexpected error
+        custom_default._cp_config = {'error_page.default': callable_error_page}
+        
         def noexist(self):
             raise cherrypy.HTTPError(404, "No, <b>really</b>, not found!")
         noexist._cp_config = {'error_page.404': "nonexistent.html"}
         finally:
             ignore.pop()
         
-        # Test custom error page.
+        # Test custom error page for a specific error.
         self.getPage("/error/custom")
         self.assertStatus(404)
         self.assertBody("Hello, world\r\n" + (" " * 499))
         
-        # Test custom error page.
+        # Test custom error page for a specific error.
         self.getPage("/error/custom?err=401")
         self.assertStatus(401)
-        self.assertBody("Well, I'm very sorry but you haven't paid!")
+        self.assertBody("Error 401 Unauthorized - Well, I'm very sorry but you haven't paid!")
+        
+        # Test default custom error page.
+        self.getPage("/error/custom_default")
+        self.assertStatus(500)
+        self.assertBody("Error 500 Internal Server Error - Well, I'm very sorry but you haven't paid!".ljust(513))
         
         # Test error in custom error page (ticket #305).
         # Note that the message is escaped for HTML (ticket #310).
         self.assertStatus(404)
         msg = ("No, &lt;b&gt;really&lt;/b&gt;, not found!<br />"
                "In addition, the custom error page failed:\n<br />"
-               "[Errno 2] No such file or directory: 'nonexistent.html'")
+               "IOError: [Errno 2] No such file or directory: 'nonexistent.html'")
         self.assertInBody(msg)
         
         if (hasattr(self, 'harness') and
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.