Package cherrypy :: Module _cperror
[hide private]
[frames] | no frames]

Source Code for Module cherrypy._cperror

  1  """Error classes for CherryPy.""" 
  2   
  3  from cgi import escape as _escape 
  4  from sys import exc_info as _exc_info 
  5  from traceback import format_exception as _format_exception 
  6  from urlparse import urljoin as _urljoin 
  7  from cherrypy.lib import http as _http 
  8   
  9   
10 -class CherryPyException(Exception):
11 pass
12 13
14 -class TimeoutError(CherryPyException):
15 """Exception raised when Response.timed_out is detected.""" 16 pass
17 18
19 -class InternalRedirect(CherryPyException):
20 """Exception raised to switch to the handler for a different URL. 21 22 Any request.params must be supplied in a query string. 23 """ 24
25 - def __init__(self, path):
26 import cherrypy 27 request = cherrypy.request 28 29 self.query_string = "" 30 if "?" in path: 31 # Separate any params included in the path 32 path, self.query_string = path.split("?", 1) 33 34 # Note that urljoin will "do the right thing" whether url is: 35 # 1. a URL relative to root (e.g. "/dummy") 36 # 2. a URL relative to the current path 37 # Note that any query string will be discarded. 38 path = _urljoin(request.path_info, path) 39 40 # Set a 'path' member attribute so that code which traps this 41 # error can have access to it. 42 self.path = path 43 44 CherryPyException.__init__(self, path, self.query_string)
45 46
47 -class HTTPRedirect(CherryPyException):
48 """Exception raised when the request should be redirected. 49 50 The new URL must be passed as the first argument to the Exception, 51 e.g., HTTPRedirect(newUrl). Multiple URLs are allowed. If a URL is 52 absolute, it will be used as-is. If it is relative, it is assumed 53 to be relative to the current cherrypy.request.path_info. 54 """ 55
56 - def __init__(self, urls, status=None):
57 import cherrypy 58 request = cherrypy.request 59 60 if isinstance(urls, basestring): 61 urls = [urls] 62 63 abs_urls = [] 64 for url in urls: 65 # Note that urljoin will "do the right thing" whether url is: 66 # 1. a complete URL with host (e.g. "http://www.example.com/test") 67 # 2. a URL relative to root (e.g. "/dummy") 68 # 3. a URL relative to the current path 69 # Note that any query string in cherrypy.request is discarded. 70 url = _urljoin(cherrypy.url(), url) 71 abs_urls.append(url) 72 self.urls = abs_urls 73 74 # RFC 2616 indicates a 301 response code fits our goal; however, 75 # browser support for 301 is quite messy. Do 302/303 instead. See 76 # http://ppewww.ph.gla.ac.uk/~flavell/www/post-redirect.html 77 if status is None: 78 if request.protocol >= (1, 1): 79 status = 303 80 else: 81 status = 302 82 else: 83 status = int(status) 84 if status < 300 or status > 399: 85 raise ValueError("status must be between 300 and 399.") 86 87 self.status = status 88 CherryPyException.__init__(self, abs_urls, status)
89
90 - def set_response(self):
91 """Modify cherrypy.response status, headers, and body to represent self. 92 93 CherryPy uses this internally, but you can also use it to create an 94 HTTPRedirect object and set its output without *raising* the exception. 95 """ 96 import cherrypy 97 response = cherrypy.response 98 response.status = status = self.status 99 100 if status in (300, 301, 302, 303, 307): 101 response.headers['Content-Type'] = "text/html" 102 # "The ... URI SHOULD be given by the Location field 103 # in the response." 104 response.headers['Location'] = self.urls[0] 105 106 # "Unless the request method was HEAD, the entity of the response 107 # SHOULD contain a short hypertext note with a hyperlink to the 108 # new URI(s)." 109 msg = {300: "This resource can be found at <a href='%s'>%s</a>.", 110 301: "This resource has permanently moved to <a href='%s'>%s</a>.", 111 302: "This resource resides temporarily at <a href='%s'>%s</a>.", 112 303: "This resource can be found at <a href='%s'>%s</a>.", 113 307: "This resource has moved temporarily to <a href='%s'>%s</a>.", 114 }[status] 115 response.body = "<br />\n".join([msg % (u, u) for u in self.urls]) 116 # Previous code may have set C-L, so we have to reset it 117 # (allow finalize to set it). 118 response.headers.pop('Content-Length', None) 119 elif status == 304: 120 # Not Modified. 121 # "The response MUST include the following header fields: 122 # Date, unless its omission is required by section 14.18.1" 123 # The "Date" header should have been set in Response.__init__ 124 125 # "...the response SHOULD NOT include other entity-headers." 126 for key in ('Allow', 'Content-Encoding', 'Content-Language', 127 'Content-Length', 'Content-Location', 'Content-MD5', 128 'Content-Range', 'Content-Type', 'Expires', 129 'Last-Modified'): 130 if key in response.headers: 131 del response.headers[key] 132 133 # "The 304 response MUST NOT contain a message-body." 134 response.body = None 135 # Previous code may have set C-L, so we have to reset it. 136 response.headers.pop('Content-Length', None) 137 elif status == 305: 138 # Use Proxy. 139 # self.urls[0] should be the URI of the proxy. 140 response.headers['Location'] = self.urls[0] 141 response.body = None 142 # Previous code may have set C-L, so we have to reset it. 143 response.headers.pop('Content-Length', None) 144 else: 145 raise ValueError("The %s status code is unknown." % status)
146
147 - def __call__(self):
148 """Use this exception as a request.handler (raise self).""" 149 raise self
150 151
152 -def 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
178 -class HTTPError(CherryPyException):
179 """ Exception used to return an HTTP error code (4xx-5xx) to the client. 180 This exception will automatically set the response status and body. 181 182 A custom message (a long description to display in the browser) 183 can be provided in place of the default. 184 """ 185
186 - def __init__(self, status=500, message=None):
187 self.status = status = int(status) 188 if status < 400 or status > 599: 189 raise ValueError("status must be between 400 and 599.") 190 # See http://www.python.org/dev/peps/pep-0352/ 191 # self.message = message 192 self._message = message 193 CherryPyException.__init__(self, status, message)
194
195 - def set_response(self):
196 """Modify cherrypy.response status, headers, and body to represent self. 197 198 CherryPy uses this internally, but you can also use it to create an 199 HTTPError object and set its output without *raising* the exception. 200 """ 201 import cherrypy 202 203 response = cherrypy.response 204 205 clean_headers(self.status) 206 207 # In all cases, finalize will be called after this method, 208 # so don't bother cleaning up response values here. 209 response.status = self.status 210 tb = None 211 if cherrypy.request.show_tracebacks: 212 tb = format_exc() 213 response.headers['Content-Type'] = "text/html" 214 215 content = self.get_error_page(self.status, traceback=tb, 216 message=self._message) 217 response.body = content 218 response.headers['Content-Length'] = len(content) 219 220 _be_ie_unfriendly(self.status)
221
222 - def get_error_page(self, *args, **kwargs):
223 return get_error_page(*args, **kwargs)
224
225 - def __call__(self):
226 """Use this exception as a request.handler (raise self).""" 227 raise self
228 229
230 -class NotFound(HTTPError):
231 """Exception raised when a URL could not be mapped to any handler (404).""" 232
233 - def __init__(self, path=None):
234 if path is None: 235 import cherrypy 236 path = cherrypy.request.script_name + cherrypy.request.path_info 237 self.args = (path,) 238 HTTPError.__init__(self, 404, "The path %r was not found." % path)
239 240 241 _HTTPErrorTemplate = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 242 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 243 <html> 244 <head> 245 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta> 246 <title>%(status)s</title> 247 <style type="text/css"> 248 #powered_by { 249 margin-top: 20px; 250 border-top: 2px solid black; 251 font-style: italic; 252 } 253 254 #traceback { 255 color: red; 256 } 257 </style> 258 </head> 259 <body> 260 <h2>%(status)s</h2> 261 <p>%(message)s</p> 262 <pre id="traceback">%(traceback)s</pre> 263 <div id="powered_by"> 264 <span>Powered by <a href="http://www.cherrypy.org">CherryPy %(version)s</a></span> 265 </div> 266 </body> 267 </html> 268 ''' 269
270 -def get_error_page(status, **kwargs):
271 """Return an HTML page, containing a pretty error response. 272 273 status should be an int or a str. 274 kwargs will be interpolated into the page template. 275 """ 276 import cherrypy 277 278 try: 279 code, reason, message = _http.valid_status(status) 280 except ValueError, x: 281 raise cherrypy.HTTPError(500, x.args[0]) 282 283 # We can't use setdefault here, because some 284 # callers send None for kwarg values. 285 if kwargs.get('status') is None: 286 kwargs['status'] = "%s %s" % (code, reason) 287 if kwargs.get('message') is None: 288 kwargs['message'] = message 289 if kwargs.get('traceback') is None: 290 kwargs['traceback'] = '' 291 if kwargs.get('version') is None: 292 kwargs['version'] = cherrypy.__version__ 293 294 for k, v in kwargs.iteritems(): 295 if v is None: 296 kwargs[k] = "" 297 else: 298 kwargs[k] = _escape(kwargs[k]) 299 300 # Use a custom template or callable for the error page? 301 pages = cherrypy.request.error_page 302 error_page = pages.get(code) or pages.get('default') 303 if error_page: 304 try: 305 if callable(error_page): 306 return error_page(**kwargs) 307 else: 308 return file(error_page, 'rb').read() % kwargs 309 except: 310 e = _format_exception(*_exc_info())[-1] 311 m = kwargs['message'] 312 if m: 313 m += "<br />" 314 m += "In addition, the custom error page failed:\n<br />%s" % e 315 kwargs['message'] = m 316 317 return _HTTPErrorTemplate % kwargs
318 319 320 _ie_friendly_error_sizes = { 321 400: 512, 403: 256, 404: 512, 405: 256, 322 406: 512, 408: 512, 409: 512, 410: 256, 323 500: 512, 501: 512, 505: 512, 324 } 325 326
327 -def _be_ie_unfriendly(status):
328 import cherrypy 329 response = cherrypy.response 330 331 # For some statuses, Internet Explorer 5+ shows "friendly error 332 # messages" instead of our response.body if the body is smaller 333 # than a given size. Fix this by returning a body over that size 334 # (by adding whitespace). 335 # See http://support.microsoft.com/kb/q218155/ 336 s = _ie_friendly_error_sizes.get(status, 0) 337 if s: 338 s += 1 339 # Since we are issuing an HTTP error status, we assume that 340 # the entity is short, and we should just collapse it. 341 content = response.collapse_body() 342 l = len(content) 343 if l and l < s: 344 # IN ADDITION: the response must be written to IE 345 # in one chunk or it will still get replaced! Bah. 346 content = content + (" " * (s - l)) 347 response.body = content 348 response.headers['Content-Length'] = len(content)
349 350
351 -def format_exc(exc=None):
352 """Return exc (or sys.exc_info if None), formatted.""" 353 if exc is None: 354 exc = _exc_info() 355 if exc == (None, None, None): 356 return "" 357 import traceback 358 return "".join(traceback.format_exception(*exc))
359
360 -def bare_error(extrabody=None):
361 """Produce status, headers, body for a critical error. 362 363 Returns a triple without calling any other questionable functions, 364 so it should be as error-free as possible. Call it from an HTTP server 365 if you get errors outside of the request. 366 367 If extrabody is None, a friendly but rather unhelpful error message 368 is set in the body. If extrabody is a string, it will be appended 369 as-is to the body. 370 """ 371 372 # The whole point of this function is to be a last line-of-defense 373 # in handling errors. That is, it must not raise any errors itself; 374 # it cannot be allowed to fail. Therefore, don't add to it! 375 # In particular, don't call any other CP functions. 376 377 body = "Unrecoverable error in the server." 378 if extrabody is not None: 379 body += "\n" + extrabody 380 381 return ("500 Internal Server Error", 382 [('Content-Type', 'text/plain'), 383 ('Content-Length', str(len(body)))], 384 [body])
385