"""Error classes for CherryPy.""" import urllib import urlparse class Error(Exception): pass class NotReady(Error): """A request was made before the app server has been started.""" pass class WrongConfigValue(Error): """ Happens when a config value can't be parsed, or is otherwise illegal. """ pass class RequestHandled(Exception): """Exception raised when no further request handling should occur.""" pass class InternalRedirect(Exception): """Exception raised when processing should be handled by a different path. If you supply 'params', it will be used to re-populate params. If 'params' is a dict, it will be used directly. If 'params' is a string, it will be converted to a dict using cgi.parse_qs. If you omit 'params', the params from the original request will remain in effect, including any POST parameters. """ def __init__(self, path, params=None): import cherrypy import cgi request = cherrypy.request # Note that urljoin will "do the right thing" whether url is: # 1. a URL relative to root (e.g. "/dummy") # 2. a URL relative to the current path # Note that any querystring will be discarded. path = urlparse.urljoin(cherrypy.request.path, path) # Set a 'path' member attribute so that code which traps this # error can have access to it. self.path = path if params is not None: if isinstance(params, basestring): request.query_string = params request.queryString = request.query_string # Backward compatibility pm = cgi.parse_qs(params, keep_blank_values=True) for key, val in pm.items(): if len(val) == 1: pm[key] = val[0] request.params = pm else: request.query_string = urllib.urlencode(params) request.queryString = request.query_string request.params = params.copy() Exception.__init__(self, path, params) class HTTPRedirect(Exception): """Exception raised when the request should be redirected. The new URL must be passed as the first argument to the Exception, e.g., cperror.HTTPRedirect(newUrl). Multiple URLs are allowed. If a URL is absolute, it will be used as-is. If it is relative, it is assumed to be relative to the current cherrypy.request.path. """ def __init__(self, urls, status=None): import cherrypy if isinstance(urls, basestring): urls = [urls] abs_urls = [] for url in urls: # Note that urljoin will "do the right thing" whether url is: # 1. a complete URL with host (e.g. "http://www.dummy.biz/test") # 2. a URL relative to root (e.g. "/dummy") # 3. a URL relative to the current path # Note that any querystring in browser_url will be discarded. url = urlparse.urljoin(cherrypy.request.browser_url, url) abs_urls.append(url) self.urls = abs_urls # RFC 2616 indicates a 301 response code fits our goal; however, # browser support for 301 is quite messy. Do 302 instead. See # http://ppewww.ph.gla.ac.uk/~flavell/www/post-redirect.html if status is None: if cherrypy.response.version >= "1.1": status = 303 else: status = 302 else: status = int(status) if status < 300 or status > 399: raise ValueError("status must be between 300 and 399.") self.status = status Exception.__init__(self, abs_urls, status) def set_response(self): import cherrypy response = cherrypy.response response.status = status = self.status if status in (300, 301, 302, 303, 307): response.headers['Content-Type'] = "text/html" # "The ... URI SHOULD be given by the Location field # in the response." response.headers['Location'] = self.urls[0] # "Unless the request method was HEAD, the entity of the response # SHOULD contain a short hypertext note with a hyperlink to the # new URI(s)." msg = {300: "This resource can be found at %s.", 301: "This resource has permanently moved to %s.", 302: "This resource resides temporarily at %s.", 303: "This resource can be found at %s.", 307: "This resource has moved temporarily to %s.", }[status] response.body = "
\n".join([msg % (u, u) for u in self.urls]) elif status == 304: # Not Modified. # "The response MUST include the following header fields: # Date, unless its omission is required by section 14.18.1" # The "Date" header should have been set in Request.__init__ # "...the response SHOULD NOT include other entity-headers. for key in ('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-Location', 'Content-MD5', 'Content-Range', 'Content-Type', 'Expires', 'Last-Modified'): if key in response.headers: del response.headers[key] # "The 304 response MUST NOT contain a message-body." response.body = None elif status == 305: # Use Proxy. # self.urls[0] should be the URI of the proxy. response.headers['Location'] = self.urls[0] response.body = None else: raise ValueError("The %s status code is unknown." % status) class HTTPError(Error): """ Exception used to return an HTTP error code to the client. This exception will automatically set the response status and body. A custom message (a long description to display in the browser) can be provided in place of the default. """ def __init__(self, status=500, message=None): self.status = status = int(status) if status < 400 or status > 599: raise ValueError("status must be between 400 and 599.") self.message = message Error.__init__(self, status, message) def set_response(self): import cherrypy handler = cherrypy._cputil.get_special_attribute("_cp_on_http_error", "_cpOnHTTPError") handler(self.status, self.message) class NotFound(HTTPError): """ Happens when a URL couldn't be mapped to any class.method """ def __init__(self, path=None): if path is None: import cherrypy path = cherrypy.request.path self.args = (path,) HTTPError.__init__(self, 404, "The path %s was not found." % repr(path)) class InternalError(HTTPError): """ Error that should never happen """ def __init__(self, message=None): HTTPError.__init__(self, 500, message)