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

Changeset 1082

Show
Ignore:
Timestamp:
05/01/06 01:40:59
Author:
fumanchu
Message:

Lots of changes to error and log handling:

  1. Removed RequestHandled? and InternalError?.
  2. Error response is now overridable by replacing request.error_response. Tools should do this in setup().
  3. New request.log_access attribute.
  4. Moved response.handleError to request.handle_error.
  5. Logging of tracebacks and request headers are now tools.
  6. New ErrorRedirect? tool class.
  7. Tools may now be anonymous (not necessary to be placed in tools module globals).
  8. Continued the move to lower_with_underscores.
Files:

Legend:

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

    r1072 r1082  
    8686    # Load _cputil lazily to avoid circular references, and 
    8787    # to allow profiler and coverage tools to work on it. 
    88     import _cputil 
     88    import _cputil, _cperror 
    8989    logfunc = _cputil.get_special_attribute('_cp_log_message') 
    9090     
    9191    if traceback: 
    92         msg += _cputil.formatExc() 
     92        msg += _cperror.format_exc() 
    9393     
    9494    logfunc(msg, context, severity) 
  • trunk/cherrypy/_cperror.py

    r1072 r1082  
    11"""Error classes for CherryPy.""" 
    22 
     3import cgi 
     4import sys 
     5import traceback 
    36import urllib 
     7import urlparse 
     8 
     9from cherrypy.lib import httptools 
     10 
    411 
    512class Error(Exception): 
     
    1421    pass 
    1522 
    16 class RequestHandled(Exception): 
    17     """Exception raised when no further request handling should occur.""" 
    18     pass 
    19  
    2023class InternalRedirect(Exception): 
    2124    """Exception raised when processing should be handled by a different path. 
     
    3134    def __init__(self, path, params=None): 
    3235        import cherrypy 
    33         import cgi 
    3436        request = cherrypy.request 
    3537         
     
    4850            else: 
    4951                request.query_string = urllib.urlencode(params) 
    50                 request.queryString = request.query_string 
    5152                request.params = params.copy() 
    5253         
     
    6566     
    6667    def __init__(self, urls, status=None): 
    67         import urlparse 
    6868        import cherrypy 
    6969         
     
    152152     
    153153    def set_response(self): 
     154        """Set cherrypy.response status, headers, and body.""" 
    154155        import cherrypy 
    155         handler = cherrypy._cputil.get_special_attribute("_cp_on_http_error") 
    156         handler(self.status, self.message) 
     156         
     157        response = cherrypy.response 
     158         
     159        # Remove headers which applied to the original content, 
     160        # but do not apply to the error page. 
     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 response.headers.has_key(key): 
     165                del response.headers[key] 
     166         
     167        if self.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 response.headers.has_key("Content-Range"): 
     175                del response.headers["Content-Range"] 
     176         
     177        # In all cases, finalize will be called after this method, 
     178        # so don't bother cleaning up response values here. 
     179        response.status = self.status 
     180        tb = None 
     181        if cherrypy.config.get('server.show_tracebacks', False): 
     182            tb = format_exc() 
     183        content = get_error_page(self.status, traceback=tb, 
     184                                 message=self.message) 
     185        response.body = content 
     186        response.headers['Content-Length'] = len(content) 
     187        response.headers['Content-Type'] = "text/html" 
     188         
     189        be_ie_unfriendly(self.status) 
    157190 
    158191 
     
    168201 
    169202 
    170 class InternalError(HTTPError): 
    171     """ Error that should never happen """ 
    172      
    173     def __init__(self, message=None): 
    174         HTTPError.__init__(self, 500, message) 
    175  
     203_HTTPErrorTemplate = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
     204<html> 
     205<head> 
     206    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta> 
     207    <title>%(status)s</title> 
     208    <style type="text/css"> 
     209    #powered_by { 
     210        margin-top: 20px; 
     211        border-top: 2px solid black; 
     212        font-style: italic; 
     213    } 
     214 
     215    #traceback { 
     216        color: red; 
     217    } 
     218    </style> 
     219</head> 
     220    <body> 
     221        <h2>%(status)s</h2> 
     222        <p>%(message)s</p> 
     223        <pre id="traceback">%(traceback)s</pre> 
     224    <div id="powered_by"> 
     225    <span>Powered by <a href="http://www.cherrypy.org">CherryPy %(version)s</a></span> 
     226    </div> 
     227    </body> 
     228</html> 
     229''' 
     230 
     231def get_error_page(status, **kwargs): 
     232    """Return an HTML page, containing a pretty error response. 
     233     
     234    status should be an int or a str. 
     235    kwargs will be interpolated into the page template. 
     236    """ 
     237    import cherrypy 
     238     
     239    try: 
     240        code, reason, message = httptools.validStatus(status) 
     241    except ValueError, x: 
     242        raise cherrypy.HTTPError(500, x.args[0]) 
     243     
     244    # We can't use setdefault here, because some 
     245    # callers send None for kwarg values. 
     246    if kwargs.get('status') is None: 
     247        kwargs['status'] = "%s %s" % (code, reason) 
     248    if kwargs.get('message') is None: 
     249        kwargs['message'] = message 
     250    if kwargs.get('traceback') is None: 
     251        kwargs['traceback'] = '' 
     252    if kwargs.get('version') is None: 
     253        kwargs['version'] = cherrypy.__version__ 
     254    for k, v in kwargs.iteritems(): 
     255        if v is None: 
     256            kwargs[k] = "" 
     257        else: 
     258            kwargs[k] = cgi.escape(kwargs[k]) 
     259     
     260    template = _HTTPErrorTemplate 
     261    error_page_file = cherrypy.config.get('error_page.%s' % code, '') 
     262    if error_page_file: 
     263        try: 
     264            template = file(error_page_file, 'rb').read() 
     265        except: 
     266            m = kwargs['message'] 
     267            if m: 
     268                m += "<br />" 
     269            m += ("In addition, the custom error page " 
     270                  "failed:\n<br />%s" % (sys.exc_info()[1])) 
     271            kwargs['message'] = m 
     272     
     273    return template % kwargs 
     274 
     275 
     276_ie_friendly_error_sizes = { 
     277    400: 512, 403: 256, 404: 512, 405: 256, 
     278    406: 512, 408: 512, 409: 512, 410: 256, 
     279    500: 512, 501: 512, 505: 512, 
     280    } 
     281 
     282 
     283def be_ie_unfriendly(status): 
     284    import cherrypy 
     285    response = cherrypy.response 
     286     
     287    # For some statuses, Internet Explorer 5+ shows "friendly error 
     288    # messages" instead of our response.body if the body is smaller 
     289    # than a given size. Fix this by returning a body over that size 
     290    # (by adding whitespace). 
     291    # See http://support.microsoft.com/kb/q218155/ 
     292    s = _ie_friendly_error_sizes.get(status, 0) 
     293    if s: 
     294        s += 1 
     295        # Since we are issuing an HTTP error status, we assume that 
     296        # the entity is short, and we should just collapse it. 
     297        content = response.collapse_body() 
     298        l = len(content) 
     299        if l and l < s: 
     300            # IN ADDITION: the response must be written to IE 
     301            # in one chunk or it will still get replaced! Bah. 
     302            content = content + (" " * (s - l)) 
     303        response.body = content 
     304        response.headers['Content-Length'] = len(content) 
     305 
     306 
     307def format_exc(exc=None): 
     308    """format_exc(exc=None) -> exc (or sys.exc_info if None), formatted.""" 
     309    if exc is None: 
     310        exc = sys.exc_info() 
     311    if exc == (None, None, None): 
     312        return "" 
     313    return "".join(traceback.format_exception(*exc)) 
     314 
     315def bare_error(extrabody=None): 
     316    """Produce status, headers, body for a critical error. 
     317     
     318    Returns a triple without calling any other questionable functions, 
     319    so it should be as error-free as possible. Call it from an HTTP server 
     320    if you get errors outside of the request. 
     321     
     322    If extrabody is None, a friendly but rather unhelpful error message 
     323    is set in the body. If extrabody is a string, it will be appended 
     324    as-is to the body. 
     325    """ 
     326     
     327    # The whole point of this function is to be a last line-of-defense 
     328    # in handling errors. That is, it must not raise any errors itself; 
     329    # it cannot be allowed to fail. Therefore, don't add to it! 
     330    # In particular, don't call any other CP functions. 
     331     
     332    body = "Unrecoverable error in the server." 
     333    if extrabody is not None: 
     334        body += "\n" + extrabody 
     335     
     336    return ("500 Internal Server Error", 
     337            [('Content-Type', 'text/plain'), 
     338             ('Content-Length', str(len(body)))], 
     339            [body]) 
     340 
  • trunk/cherrypy/_cprequest.py

    r1080 r1082  
    77 
    88import cherrypy 
    9 from cherrypy import _cputil, _cpcgifs, tools 
     9from cherrypy import _cperror, _cputil, _cpcgifs, tools 
    1010from cherrypy.lib import cptools, httptools 
    1111 
     
    2222        scheme should be a string, either "http" or "https". 
    2323        """ 
    24         self.remote_addr = remote_addr 
    25         self.remote_port = remote_port 
    26         self.remote_host = remote_host 
     24        self.remote_addr = remote_addr 
     25        self.remote_port = remote_port 
     26        self.remote_host = remote_host 
    2727         
    2828        self.scheme = scheme 
     
    4343            cherrypy.serving.__dict__.clear() 
    4444     
    45     def run(self, requestLine, headers, rfile): 
     45    def run(self, request_line, headers, rfile): 
    4646        """Process the Request. 
    4747         
    48         requestLine should be of the form "GET /path HTTP/1.0". 
     48        request_line should be of the form "GET /path HTTP/1.0". 
    4949        headers should be a list of (name, value) tuples. 
    5050        rfile should be a file-like object containing the HTTP request 
     
    6060         
    6161        """ 
    62         self.requestLine = requestLine.strip() 
     62        self.log_access = _cputil.log_access 
     63        self.error_response = cherrypy.HTTPError(500).set_response 
     64         
     65        self.request_line = request_line.strip() 
    6366        self.header_list = list(headers) 
    6467        self.rfile = rfile 
    65          
    6668        self.headers = httptools.HeaderMap() 
    6769        self.simple_cookie = Cookie.SimpleCookie() 
     
    7678            cherrypy.response.body = [] 
    7779         
    78         _cputil.get_special_attribute("_cp_log_access")() 
     80        self.log_access() 
    7981         
    8082        return cherrypy.response 
    8183     
    8284    def _run(self): 
     85        conf = cherrypy.config.get 
    8386         
    8487        try: 
     
    8689            # because request.object_path is used for config lookups 
    8790            # right away. 
    88             self.processRequestLine() 
    89             self.dispatch = cherrypy.config.get("dispatch") or _cputil.dispatch 
     91            self.process_request_line() 
     92            self.dispatch = conf("dispatch") or _cputil.dispatch 
    9093            self.hooks.setup() 
    9194             
     
    9497                 
    9598                try: 
    96                     self.processHeaders() 
     99                    self.process_headers() 
    97100                     
    98101                    self.hooks.run('before_request_body') 
    99                     if self.processRequestBody: 
    100                         self.processBody() 
     102                    if self.process_request_body: 
     103                        self.process_body() 
    101104                     
    102105                    # Loop to allow for InternalRedirect. 
     
    112115                    self.hooks.run('before_finalize') 
    113116                    cherrypy.response.finalize() 
    114                 except cherrypy.RequestHandled: 
    115                     pass 
    116117                except (cherrypy.HTTPRedirect, cherrypy.HTTPError), inst: 
    117118                    # For an HTTPRedirect or HTTPError (including NotFound), 
     
    126127            raise 
    127128        except: 
    128             if cherrypy.config.get("server.throw_errors", False): 
     129            if conf("server.throw_errors", False): 
    129130                raise 
    130             cherrypy.response.handleError(sys.exc_info()) 
    131      
    132     def processRequestLine(self): 
    133         rl = self.requestLine 
    134         method, path, qs, proto = httptools.parseRequestLine(rl) 
     131            self.handle_error(sys.exc_info()) 
     132     
     133    def process_request_line(self): 
     134        """Parse the first line (e.g. "GET /path HTTP/1.1") of the request.""" 
     135        rl = self.request_line 
     136        method, path, qs, proto = httptools.parse_request_line(rl) 
    135137        if path == "*": 
    136138            path = "global" 
    137139         
    138140        self.method = method 
    139         self.processRequestBody = method in ("POST", "PUT") 
     141        self.process_request_body = method in ("POST", "PUT") 
    140142         
    141143        self.path = path 
     
    169171        cherrypy.response.version = min(self.version, server_v) 
    170172     
    171     def processHeaders(self): 
     173    def process_headers(self): 
    172174        self.params = httptools.parseQueryString(self.query_string) 
    173175         
     
    202204                          doc="The URL as entered in a browser (read-only).") 
    203205     
    204     def processBody(self): 
     206    def process_body(self): 
     207        """Convert request.rfile into request.params (or request.body).""" 
    205208        # Create a copy of headers with lowercase keys because 
    206209        # FieldStorage doesn't work otherwise 
     
    225228        else: 
    226229            self.params.update(httptools.paramsFromCGIForm(forms)) 
     230     
     231    def handle_error(self, exc): 
     232        response = cherrypy.response 
     233        try: 
     234            self.hooks.run("before_error_response") 
     235            if self.error_response: 
     236                self.error_response() 
     237            self.hooks.run("after_error_response") 
     238            response.finalize() 
     239            return 
     240        except cherrypy.HTTPRedirect, inst: 
     241            try: 
     242                inst.set_response() 
     243                response.finalize() 
     244                return 
     245            except (KeyboardInterrupt, SystemExit): 
     246                raise 
     247            except: 
     248                # Fall through to the second error handler 
     249                pass 
     250        except (KeyboardInterrupt, SystemExit): 
     251            raise 
     252        except: 
     253            # Fall through to the second error handler 
     254            pass 
     255         
     256        # Failure in error handler or finalize. Bypass them. 
     257        if cherrypy.config.get('server.show_tracebacks', False): 
     258            dbltrace = ("\n===First Error===\n\n%s" 
     259                        "\n\n===Second Error===\n\n%s\n\n") 
     260            body = dbltrace % (_cperror.format_exc(exc), 
     261                               _cperror.format_exc()) 
     262        else: 
     263            body = "" 
     264        r = _cperror.bare_error(body) 
     265        response.status, response.header_list, response.body = r 
    227266 
    228267 
     
    325364                name, value = line.split(": ", 1) 
    326365                self.header_list.append((name, value)) 
    327      
    328     dbltrace = "\n===First Error===\n\n%s\n\n===Second Error===\n\n%s\n\n" 
    329      
    330     def handleError(self, exc): 
    331         """Set status, headers, and body when an unanticipated error occurs.""" 
    332         try: 
    333             cherrypy.request.hooks.run('before_error_response') 
    334              
    335             _cputil.get_special_attribute('_cp_on_error')() 
    336             self.finalize() 
    337              
    338             cherrypy.request.hooks.run('after_error_response') 
    339             return 
    340         except cherrypy.HTTPRedirect, inst: 
    341             try: 
    342                 inst.set_response() 
    343                 self.finalize() 
    344                 return 
    345             except (KeyboardInterrupt, SystemExit): 
    346                 raise 
    347             except: 
    348                 # Fall through to the second error handler 
    349                 pass 
    350         except (KeyboardInterrupt, SystemExit): 
    351             raise 
    352         except: 
    353             # Fall through to the second error handler 
    354             pass 
    355          
    356         # Failure in error hooks or finalize. 
    357         # Bypass them all. 
    358         if cherrypy.config.get('server.show_tracebacks', False): 
    359             body = self.dbltrace % (_cputil.formatExc(exc), 
    360                                     _cputil.formatExc()) 
    361         else: 
    362             body = "" 
    363         self.setBareError(body) 
    364      
    365     def setBareError(self, body=None): 
    366         self.status, self.header_list, self.body = _cputil.bareError(body) 
    367  
  • trunk/cherrypy/_cputil.py

    r1080 r1082  
    117117def get_special_attribute(name): 
    118118    """Return the special attribute. A special attribute is one that 
    119     applies to all of the children from where it is defined, such as 
    120     _cp_on_error.""" 
     119    applies to all of the children from where it is defined.""" 
    121120     
    122121    # First, we look in the right-most object to see if this special 
     
    153152_cpGlobalHandler.exposed = True 
    154153 
     154 
    155155def logtime(): 
    156156    now = datetime.datetime.now() 
     
    159159        now.day, month, now.year, now.hour, now.minute, now.second) 
    160160 
    161 def _cp_log_access(): 
     161def log_access(): 
    162162    """Default method for logging access""" 
     163    request = cherrypy.request 
    163164     
    164165    tmpl = '%(h)s %(l)s %(u)s [%(t)s] "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' 
    165     s = tmpl % {'h': cherrypy.request.remote_host, 
     166    s = tmpl % {'h': request.remote_host, 
    166167                'l': '-', 
    167                 'u': getattr(cherrypy.request, "login", None) or "-", 
     168                'u': getattr(request, "login", None) or "-", 
    168169                't': logtime(), 
    169                 'r': cherrypy.request.requestLine, 
     170                'r': request.request_line, 
    170171                's': cherrypy.response.status.split(" ", 1)[0], 
    171172                'b': cherrypy.response.headers.get('Content-Length', '') or "-", 
    172                 'f': cherrypy.request.headers.get('referer', ''), 
    173                 'a': cherrypy.request.headers.get('user-agent', ''), 
     173                'f': request.headers.get('referer', ''), 
     174                'a': request.headers.get('user-agent', ''), 
    174175                } 
    175176     
     
    182183        f.write(s + '\n') 
    183184        f.close() 
    184  
    185185 
    186186_log_severity_levels = {0: "INFO", 1: "WARNING", 2: "ERROR"} 
     
    209209        f.close() 
    210210 
    211  
    212 _HTTPErrorTemplate = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
    213 <html> 
    214 <head> 
    215     <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta> 
    216     <title>%(status)s</title> 
    217     <style type="text/css"> 
    218     #powered_by { 
    219         margin-top: 20px; 
    220         border-top: 2px solid black; 
    221         font-style: italic; 
    222     } 
    223  
    224     #traceback { 
    225         color: red; 
    226     } 
    227     </style> 
    228 </head> 
    229     <body> 
    230         <h2>%(status)s</h2> 
    231         <p>%(message)s</p> 
    232         <pre id="traceback">%(traceback)s</pre> 
    233     <div id="powered_by"> 
    234     <span>Powered by <a href="http://www.cherrypy.org">CherryPy %(version)s</a></span> 
    235     </div> 
    236     </body> 
    237 </html> 
    238 ''' 
    239  
    240 def getErrorPage(status, **kwargs): 
    241     """Return an HTML page, containing a pretty error response. 
    242      
    243     status should be an int or a str. 
    244     kwargs will be interpolated into the page template. 
    245     """ 
    246      
    247     try: 
    248         code, reason, message = httptools.validStatus(status) 
    249     except ValueError, x: 
    250         raise cherrypy.HTTPError(500, x.args[0]) 
    251      
    252     # We can't use setdefault here, because some 
    253     # callers send None for kwarg values. 
    254     if kwargs.get('status') is None: 
    255         kwargs['status'] = "%s %s" % (code, reason) 
    256     if kwargs.get('message') is None: 
    257         kwargs['message'] = message 
    258     if kwargs.get('traceback') is None: 
    259         kwargs['traceback'] = '' 
    260     if kwargs.get('version') is None: 
    261         kwargs['version'] = cherrypy.__version__ 
    262     for k, v in kwargs.iteritems(): 
    263         if v is None: 
    264             kwargs[k] = "" 
    265         else: 
    266             kwargs[k] = cgi.escape(kwargs[k]) 
    267      
    268     template = _HTTPErrorTemplate 
    269     error_page_file = cherrypy.config.get('error_page.%s' % code, '') 
    270     if error_page_file: 
    271         try: 
    272             template = file(error_page_file, 'rb').read() 
    273         except: 
    274             m = kwargs['message'] 
    275             if m: 
    276                 m += "<br />" 
    277             m += ("In addition, the custom error page " 
    278                   "failed:\n<br />%s" % (sys.exc_info()[1])) 
    279             kwargs['message'] = m 
    280      
    281     return template % kwargs 
    282  
    283 def _cp_on_error(): 
    284     """ Default _cp_on_error method """ 
    285     # Allow logging of only *unexpected* HTTPError's. 
    286     if (not cherrypy.config.get('server.log_tracebacks', True) 
    287         and cherrypy.config.get('server.log_unhandled_tracebacks', True)): 
    288         cherrypy.log(traceback=True) 
    289     cherrypy.HTTPError(500).set_response() 
    290  
    291 def _cp_on_http_error(status, message): 
    292     """Default _cp_on_http_error method. 
    293      
    294     status should be an int. 
    295     """ 
    296     tb = formatExc() 
    297     logmsg = "" 
    298      
    299     if cherrypy.config.get('server.log_tracebacks', True): 
    300         logmsg = tb 
    301     if cherrypy.config.get('server.log_request_headers', True): 
    302         h = ["  %s: %s" % (k, v) for k, v in cherrypy.request.header_list] 
    303         logmsg += 'Request Headers:\n' + '\n'.join(h) 
    304     if logmsg: 
    305         cherrypy.log(logmsg, "HTTP") 
    306      
    307     if not cherrypy.config.get('server.show_tracebacks', False): 
    308         tb = None 
    309      
    310     response = cherrypy.response 
    311      
    312     # Remove headers which applied to the original content, 
    313     # but do not apply to the error page. 
    314     for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After", 
    315                 "Vary", "Content-Encoding", "Content-Length", "Expires", 
    316                 "Content-Location", "Content-MD5", "Last-Modified"]: 
    317         if response.headers.has_key(key): 
    318             del response.headers[key] 
    319      
    320     if status != 416: 
    321         # A server sending a response with status code 416 (Requested 
    322         # range not satisfiable) SHOULD include a Content-Range field 
    323         # with a byte-range- resp-spec of "*". The instance-length 
    324         # specifies the current length of the selected resource. 
    325         # A response with status code 206 (Partial Content) MUST NOT 
    326         # include a Content-Range field with a byte-range- resp-spec of "*". 
    327         if response.headers.has_key("Content-Range"): 
    328             del response.headers["Content-Range"] 
    329      
    330     # In all cases, finalize will be called after this method, 
    331     # so don't bother cleaning up response values here. 
    332     response.status = status 
    333     content = getErrorPage(status, traceback=tb, message=message) 
    334     response.body = content 
    335     response.headers['Content-Length'] = len(content) 
    336     response.headers['Content-Type'] = "text/html" 
    337      
    338     be_ie_unfriendly(status) 
    339  
    340  
    341 _ie_friendly_error_sizes = { 
    342     400: 512, 403: 256, 404: 512, 405: 256, 
    343     406: 512, 408: 512, 409: 512, 410: 256, 
    344     500: 512, 501: 512, 505: 512, 
    345     } 
    346  
    347  
    348 def be_ie_unfriendly(status): 
    349      
    350     response = cherrypy.response 
    351      
    352     # For some statuses, Internet Explorer 5+ shows "friendly error 
    353     # messages" instead of our response.body if the body is smaller 
    354     # than a given size. Fix this by returning a body over that size 
    355     # (by adding whitespace). 
    356     # See http://support.microsoft.com/kb/q218155/ 
    357     s = _ie_friendly_error_sizes.get(status, 0) 
    358     if s: 
    359         s += 1 
    360         # Since we are issuing an HTTP error status, we assume that 
    361         # the entity is short, and we should just collapse it. 
    362         content = response.collapse_body() 
    363         l = len(content) 
    364         if l and l < s: 
    365             # IN ADDITION: the response must be written to IE 
    366             # in one chunk or it will still get replaced! Bah. 
    367             content = content + (" " * (s - l)) 
    368         response.body = content 
    369         response.headers['Content-Length'] = len(content) 
    370  
    371 def lower_to_camel(s): 
    372     """Turns lowercase_with_underscore into camelCase.""" 
    373     sp = s.split('_') 
    374     new_sp = [] 
    375     for i, s in enumerate(sp): 
    376         if i != 0: 
    377             s = s[0].upper() + s[1:] 
    378         new_sp.append(s) 
    379     return ''.join(new_sp) 
    380  
    381 def formatExc(exc=None): 
    382     """formatExc(exc=None) -> exc (or sys.exc_info if None), formatted.""" 
    383     if exc is None: 
    384         exc = sys.exc_info() 
    385      
    386     if exc == (None, None, None): 
    387         return "" 
    388      
    389     page_handler_str = "" 
    390     args = list(getattr(exc[1], "args", [])) 
    391     if args: 
    392         if len(args) > 1: 
    393             page_handler = args.pop() 
    394             page_handler_str = 'Page handler: %s\n' % repr(page_handler) 
    395             exc[1].args = tuple(args) 
    396     return page_handler_str + "".join(traceback.format_exception(*exc)) 
    397  
    398 def bareError(extrabody=None): 
    399     """Produce status, headers, body for a critical error. 
    400      
    401     Returns a triple without calling any other questionable functions, 
    402     so it should be as error-free as possible. Call it from an HTTP server 
    403     if you get errors after Request() is done. 
    404      
    405     If extrabody is None, a friendly but rather unhelpful error message 
    406     is set in the body. If extrabody is a string, it will be appended 
    407     as-is to the body. 
    408     """ 
    409      
    410     # The whole point of this function is to be a last line-of-defense 
    411     # in handling errors. That is, it must not raise any errors itself; 
    412     # it cannot be allowed to fail. Therefore, don't add to it! 
    413     # In particular, don't call any other CP functions. 
    414      
    415     body = "Unrecoverable error in the server." 
    416     if extrabody is not None: 
    417         body += "\n" + extrabody 
    418      
    419     return ("500 Internal Server Error", 
    420             [('Content-Type', 'text/plain'), 
    421              ('Content-Length', str(len(body)))], 
    422             [body]) 
    423  
  • trunk/cherrypy/_cpwsgi.py

    r1013 r1082  
    44import cherrypy 
    55from cherrypy import _cputil, _cpwsgiserver 
     6from cherrypy._cperror import format_exc, bare_error 
    67from cherrypy.lib import httptools 
    78 
    89 
    9 def requestLine(environ): 
     10def request_line(environ): 
    1011    """Rebuild first line of the request (e.g. "GET /path HTTP/1.0").""" 
    1112     
     
    7172        request.multiprocess = environ['wsgi.multiprocess'] 
    7273        request.wsgi_environ = environ 
    73         response = request.run(requestLine(environ), 
     74        response = request.run(request_line(environ), 
    7475                               translate_headers(environ), 
    7576                               environ['wsgi.input']) 
     
    8182        if cherrypy.config.get("server.throw_errors", False): 
    8283            raise 
    83         tb = _cputil.formatExc() 
     84        tb = format_exc() 
    8485        cherrypy.log(tb) 
    8586        if not cherrypy.config.get("server.show_tracebacks", False): 
    8687            tb = "" 
    87         s, h, b = _cputil.bareError(tb) 
     88        s, h, b = bare_error(tb) 
    8889        exc = sys.exc_info() 
    8990     
     
    115116            cherrypy.log(traceback=True) 
    116117        request = None 
    117         s, h, b = _cputil.bareError() 
    118         # CherryPy test suite expects bareError body to be output, 
     118        s, h, b = bare_error() 
     119        # CherryPy test suite expects bare_error body to be output, 
    119120        # so don't call start_response (which, according to PEP 333, 
    120121        # may raise its own error at that point). 
  • trunk/cherrypy/config.py

    r1069 r1082  
    2121    'server.protocol_version': 'HTTP/1.0', 
    2222    'server.log_to_screen': True, 
    23     'server.log_tracebacks': True, 
    2423    'server.log_file': '', 
     24    'tools.log_tracebacks.on': True, 
    2525    'server.reverse_dns': False, 
    2626    'server.thread_pool': 10, 
     
    230230                  'server.log_to_screen', 
    231231                  'server.log_file', 
    232                   'server.log_tracebacks', 
    233                   'server.log_request_headers', 
    234232                  'server.protocol_version', 
    235233                  'server.socket_host', 
  • trunk/cherrypy/lib/cptools.py

    r1070 r1082  
    77 
    88import cherrypy 
    9  
    109 
    1110 
     
    315314        cherrypy.request.object_path = prefix + "/" + cherrypy.request.object_path 
    316315 
    317  
     316def log_traceback(): 
     317    """Write the last error's traceback to the cherrypy error log.""" 
     318    from cherrypy import _cperror 
     319    cherrypy.log(_cperror.format_exc(), "HTTP") 
     320 
     321def log_request_headers(): 
     322    """Write the last error's traceback to the cherrypy error log.""" 
     323    h = ["  %s: %s" % (k, v) for k, v in cherrypy.request.header_list] 
     324    cherrypy.log('\nRequest Headers:\n' + '\n'.join(h), "HTTP") 
     325 
     326def redirect(url=''): 
     327    """Raise cherrypy.HTTPRedirect to the given url.""" 
     328    raise cherrypy.HTTPRedirect(url) 
  • trunk/cherrypy/lib/httptools.py

    r1015 r1082  
    304304    return code, reason, message 
    305305 
    306 def parseRequestLine(requestLine): 
    307     """Return (method, path, querystring, protocol) from a requestLine.""" 
    308     method, path, protocol = requestLine.split() 
     306def parse_request_line(request_line): 
     307    """Return (method, path, querystring, protocol) from a request_line.""" 
     308    method, path, protocol = request_line.split() 
    309309     
    310310    # path may be an abs_path (including "http://host.domain.tld"); 
  • trunk/cherrypy/lib/sessions.py

    r1079 r1082  
    362362 
    363363# Users access sessions through cherrypy.session, but we want this 
    364 #   to be thread-specific so we use a special wrapper that forward
    365 #   calls to cherrypy.session to a thread-specific dictionary called 
     364#   to be thread-specific so we use a wrapper that forwards call
     365#   to cherrypy.session to a thread-specific dictionary called 
    366366#   cherrypy.request._session.data 
    367367class SessionWrapper: 
  • trunk/cherrypy/lib/wsgiapp.py

    r1070 r1082  
    9292        """A custom Request object for running a WSGI app within CP.""" 
    9393        
    94         def processBody(self): 
     94        def process_body(self): 
    9595            pass 
    9696         
  • trunk/cherrypy/lib/xmlrpc.py

    r1075 r1082  
    4141                                  allow_none=allow_none)) 
    4242 
    43 def wrap_error(): 
     43def on_error(): 
    4444    body = str(sys.exc_info()[1]) 
    4545    _set_response(xmlrpclib.dumps(xmlrpclib.Fault(1, body))) 
  • trunk/cherrypy/test/benchmark.py

    r1070 r1082  
    8787        pass 
    8888     
    89     def run(self, requestLine, headers, rfile): 
     89    def run(self, request_line, headers, rfile): 
    9090        cherrypy.response.status = "204 No Content" 
    9191        cherrypy.response.header_list = [("Content-Type", 'text/html'), 
  • trunk/cherrypy/test/helper.py

    r1035 r1082  
    5555         
    5656        # This will never contain a traceback: 
    57         page = cherrypy._cputil.getErrorPage(status, message=message) 
     57        page = cherrypy._cperror.get_error_page(status, message=message) 
    5858         
    5959        # First, test the response body without checking the traceback. 
  • trunk/cherrypy/test/test_config.py

    r1069 r1082  
    9595        err = ('WrongConfigValue: ("section: ' 
    9696               "'global', option: 'server.environment', value: 'production'" 
    97                '''", 'UnknownType')''') 
     97               '''", 'UnknownType', ('production',))''') 
    9898        self.getPage("/env/wrong") 
    9999        self.assertErrorPage(500, pattern=err) 
  • trunk/cherrypy/test/test_core.py

    r1075 r1082  
    105105         
    106106        class Error: 
    107             def _cp_on_error(self): 
    108                 raise cherrypy.HTTPRedirect("/errpage") 
     107            _cp_tools = [tools.ErrorRedirect("/errpage")] 
    109108             
    110109            def index(self): 
     
    207206            # Since status must start with an int, this should error. 
    208207            cherrypy.response.status = "ZOO OK" 
    209          
    210         def log_unhandled(self): 
    211             raise ValueError() 
    212208         
    213209        def rethrow(self): 
     
    356352        '/error': { 
    357353            'server.log_file': log_file, 
    358             'server.log_tracebacks': True, 
     354            'tools.log_tracebacks.on': True, 
    359355        }, 
    360356        '/error/page_streamed': { 
     
    370366            'error_page.404': "nonexistent.html", 
    371367        }, 
    372         '/error/log_unhandled': { 
    373             'server.log_tracebacks': False, 
    374             'server.log_unhandled_tracebacks': True, 
    375         }, 
    376368        '/error/rethrow': { 
    377369            'server.throw_errors': True, 
     
    488480            self.assertEqual(data[0][-41:], ' INFO Traceback (most recent call last):\n') 
    489481            self.assertEqual(data[-3], '    raise ValueError()\n') 
    490              
    491             # Test that unhandled tracebacks get written