Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

Changeset 1949

Show
Ignore:
Timestamp:
04/26/08 17:50:08
Author:
fumanchu
Message:

Fix for #800 (ability to override default error template). Many thanks to Scott Chapman for the ideas and Nicolas Grilly for the fix.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/cherrypy/_cperror.py

    r1839 r1949  
    33from cgi import escape as _escape 
    44from sys import exc_info as _exc_info 
     5from traceback import format_exception as _format_exception 
    56from urlparse import urljoin as _urljoin 
    67from cherrypy.lib import http as _http 
     
    149150 
    150151 
     152def clean_headers(status): 
     153    """Remove any headers which should not apply to an error response.""" 
     154    import cherrypy 
     155     
     156    response = cherrypy.response 
     157     
     158    # Remove headers which applied to the original content, 
     159    # but do not apply to the error page. 
     160    respheaders = response.headers 
     161    for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After", 
     162                "Vary", "Content-Encoding", "Content-Length", "Expires", 
     163                "Content-Location", "Content-MD5", "Last-Modified"]: 
     164        if respheaders.has_key(key): 
     165            del respheaders[key] 
     166     
     167    if status != 416: 
     168        # A server sending a response with status code 416 (Requested 
     169        # range not satisfiable) SHOULD include a Content-Range field 
     170        # with a byte-range-resp-spec of "*". The instance-length 
     171        # specifies the current length of the selected resource. 
     172        # A response with status code 206 (Partial Content) MUST NOT 
     173        # include a Content-Range field with a byte-range- resp-spec of "*". 
     174        if respheaders.has_key("Content-Range"): 
     175            del respheaders["Content-Range"] 
     176 
     177 
    151178class HTTPError(CherryPyException): 
    152179    """ Exception used to return an HTTP error code (4xx-5xx) to the client. 
     
    174201        response = cherrypy.response 
    175202         
    176         # Remove headers which applied to the original content, 
    177         # but do not apply to the error page. 
    178         respheaders = response.headers 
    179         for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After", 
    180                     "Vary", "Content-Encoding", "Content-Length", "Expires", 
    181                     "Content-Location", "Content-MD5", "Last-Modified"]: 
    182             if respheaders.has_key(key): 
    183                 del respheaders[key] 
    184          
    185         if self.status != 416: 
    186             # A server sending a response with status code 416 (Requested 
    187             # range not satisfiable) SHOULD include a Content-Range field 
    188             # with a byte-range-resp-spec of "*". The instance-length 
    189             # specifies the current length of the selected resource. 
    190             # A response with status code 206 (Partial Content) MUST NOT 
    191             # include a Content-Range field with a byte-range- resp-spec of "*". 
    192             if respheaders.has_key("Content-Range"): 
    193                 del respheaders["Content-Range"] 
     203        clean_headers(self.status) 
    194204         
    195205        # In all cases, finalize will be called after this method, 
     
    199209        if cherrypy.request.show_tracebacks: 
    200210            tb = format_exc() 
    201         respheaders['Content-Type'] = "text/html" 
     211        response.headers['Content-Type'] = "text/html" 
    202212         
    203213        content = self.get_error_page(self.status, traceback=tb, 
    204214                                      message=self.message) 
    205215        response.body = content 
    206         respheaders['Content-Length'] = len(content) 
     216        response.headers['Content-Length'] = len(content) 
    207217         
    208218        _be_ie_unfriendly(self.status) 
     
    279289    if kwargs.get('version') is None: 
    280290        kwargs['version'] = cherrypy.__version__ 
     291     
    281292    for k, v in kwargs.iteritems(): 
    282293        if v is None: 
     
    285296            kwargs[k] = _escape(kwargs[k]) 
    286297     
    287     template = _HTTPErrorTemplate 
    288      
    289     # Replace the default template with a custom one? 
    290     error_page_file = cherrypy.request.error_page.get(code, '') 
    291     if error_page_file: 
     298    # Use a custom template or callable for the error page? 
     299    pages = cherrypy.request.error_page 
     300    error_page = pages.get(code) or pages.get('default') 
     301    if error_page: 
    292302        try: 
    293             template = file(error_page_file, 'rb').read() 
     303            if callable(error_page): 
     304                return error_page(**kwargs) 
     305            else: 
     306                return file(error_page, 'rb').read() % kwargs 
    294307        except: 
     308            e = _format_exception(*_exc_info())[-1] 
    295309            m = kwargs['message'] 
    296310            if m: 
    297311                m += "<br />" 
    298             m += ("In addition, the custom error page " 
    299                   "failed:\n<br />%s" % (_exc_info()[1])) 
     312            m += "In addition, the custom error page failed:\n<br />%s" % e 
    300313            kwargs['message'] = m 
    301314     
    302     return template % kwargs 
     315    return _HTTPErrorTemplate % kwargs 
    303316 
    304317 
     
    369382            [body]) 
    370383 
     384 
  • trunk/cherrypy/_cprequest.py

    r1938 r1949  
    146146def error_page_namespace(k, v): 
    147147    """Attach error pages declared in config.""" 
    148     cherrypy.request.error_page[int(k)] = v 
     148    if k != 'default': 
     149        k = int(k) 
     150    cherrypy.request.error_page[k] = v 
    149151 
    150152 
     
    393395    error_page = {} 
    394396    error_page__doc = """ 
    395     A dict of {error code: response filename} pairs. The named response 
    396     files should be Python string-formatting templates, and can expect by 
    397     default to receive the format values with the mapping keys 'status', 
    398     'message', 'traceback', and 'version'. The set of format mappings 
    399     can be extended by overriding HTTPError.set_response.""" 
     397    A dict of {error code: response filename or callable} pairs. 
     398     
     399    The error code must be an int representing a given HTTP error code, 
     400    or the string 'default', which will be used if no matching entry 
     401    is found for a given numeric code. 
     402     
     403    If a filename is provided, the file should contain a Python string- 
     404    formatting template, and can expect by default to receive format  
     405    values with the mapping keys %(status)s, %(message)s, %(traceback)s, 
     406    and %(version)s. The set of format mappings can be extended by 
     407    overriding HTTPError.set_response. 
     408     
     409    If a callable is provided, it will be called by default with keyword  
     410    arguments 'status', 'message', 'traceback', and 'version', as for a 
     411    string-formatting template. The callable must return a string which 
     412    will be set to response.body. It may also override headers or perform 
     413    any other processing. 
     414     
     415    If no entry is given for an error code, and no 'default' entry exists, 
     416    a default template will be used. 
     417    """ 
    400418     
    401419    show_tracebacks = True 
     
    716734     
    717735    def handle_error(self, exc): 
    718         """Handle the last exception. (Core)""" 
     736        """Handle the last unanticipated exception. (Core)""" 
    719737        try: 
    720738            self.hooks.run("before_error_response") 
     
    884902 
    885903 
     904 
  • trunk/cherrypy/test/test_core.py

    r1921 r1949  
    240240            for chunk in self.as_yield(): 
    241241                yield chunk 
    242  
    243  
     242     
     243     
     244    def callable_error_page(status, **kwargs): 
     245        return "Error %s - Well, I'm very sorry but you haven't paid!" % status 
     246     
     247     
    244248    class Error(Test): 
    245249         
     
    247251                      } 
    248252         
    249         def custom(self): 
    250             raise cherrypy.HTTPError(404, "No, <b>really</b>, not found!") 
    251         custom._cp_config = {'error_page.404': os.path.join(localDir, "static/index.html")} 
     253        def custom(self, err='404'): 
     254            raise cherrypy.HTTPError(int(err), "No, <b>really</b>, not found!") 
     255        custom._cp_config = {'error_page.404': os.path.join(localDir, "static/index.html"), 
     256                             'error_page.401': callable_error_page, 
     257                             } 
     258         
     259        def custom_default(self): 
     260            return 1 + 'a' # raise an unexpected error 
     261        custom_default._cp_config = {'error_page.default': callable_error_page} 
    252262         
    253263        def noexist(self): 
     
    721731            ignore.pop() 
    722732         
    723         # Test custom error page
     733        # Test custom error page for a specific error
    724734        self.getPage("/error/custom") 
    725735        self.assertStatus(404) 
    726736        self.assertBody("Hello, world\r\n" + (" " * 499)) 
     737         
     738        # Test custom error page for a specific error. 
     739        self.getPage("/error/custom?err=401") 
     740        self.assertStatus(401) 
     741        self.assertBody("Error 401 Unauthorized - Well, I'm very sorry but you haven't paid!") 
     742         
     743        # Test default custom error page. 
     744        self.getPage("/error/custom_default") 
     745        self.assertStatus(500) 
     746        self.assertBody("Error 500 Internal Server Error - Well, I'm very sorry but you haven't paid!".ljust(513)) 
    727747         
    728748        # Test error in custom error page (ticket #305). 
     
    732752        msg = ("No, &lt;b&gt;really&lt;/b&gt;, not found!<br />" 
    733753               "In addition, the custom error page failed:\n<br />" 
    734                "[Errno 2] No such file or directory: 'nonexistent.html'") 
     754               "IOError: [Errno 2] No such file or directory: 'nonexistent.html'") 
    735755        self.assertInBody(msg) 
    736756         

Hosted by WebFaction

Log in as guest/cpguest to create tickets