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

root/branches/cp3-wsgi-remix/_cperror.py

Revision 1243 (checked in by fumanchu, 2 years ago)

Lots of mixedCase to lower_with_underscores.

  • Property svn:eol-style set to native
Line 
1 """Error classes for CherryPy."""
2
3 from cgi import escape as _escape
4 from sys import exc_info as _exc_info
5 from urlparse import urljoin as _urljoin
6 from cherrypy.lib import http as _http
7
8
9 class WrongConfigValue(Exception):
10     """ Happens when a config value can't be parsed, or is otherwise illegal. """
11     pass
12
13 class InternalRedirect(Exception):
14     """Exception raised when processing should be handled by a different path.
15     
16     If you supply a query string, it will be replace request.params.
17     If you omit the query string, the params from the original request will
18     remain in effect.
19     """
20    
21     def __init__(self, path):
22         import cherrypy
23         request = cherrypy.request
24        
25         if "?" in path:
26             # Pop any params included in the path
27             path, pm = path.split("?", 1)
28             request.query_string = pm
29             request.params = _http.parse_query_string(pm)
30        
31         # Note that urljoin will "do the right thing" whether url is:
32         #  1. a URL relative to root (e.g. "/dummy")
33         #  2. a URL relative to the current path
34         # Note that any querystring will be discarded.
35         path = _urljoin(cherrypy.request.path_info, path)
36        
37         # Set a 'path' member attribute so that code which traps this
38         # error can have access to it.
39         self.path = path
40        
41         Exception.__init__(self, path)
42
43
44
45 class HTTPRedirect(Exception):
46     """Exception raised when the request should be redirected.
47     
48     The new URL must be passed as the first argument to the Exception, e.g.,
49         cperror.HTTPRedirect(newUrl). Multiple URLs are allowed. If a URL
50         is absolute, it will be used as-is. If it is relative, it is assumed
51         to be relative to the current cherrypy.request.path.
52     """
53    
54     def __init__(self, urls, status=None):
55         import cherrypy
56        
57         if isinstance(urls, basestring):
58             urls = [urls]
59        
60         abs_urls = []
61         for url in urls:
62             # Note that urljoin will "do the right thing" whether url is:
63             #  1. a complete URL with host (e.g. "http://www.dummy.biz/test")
64             #  2. a URL relative to root (e.g. "/dummy")
65             #  3. a URL relative to the current path
66             # Note that any querystring in browser_url will be discarded.
67             url = _urljoin(cherrypy.request.browser_url, url)
68             abs_urls.append(url)
69         self.urls = abs_urls
70        
71         # RFC 2616 indicates a 301 response code fits our goal; however,
72         # browser support for 301 is quite messy. Do 302/303 instead. See
73         # http://ppewww.ph.gla.ac.uk/~flavell/www/post-redirect.html
74         if status is None:
75             if cherrypy.request.protocol >= (1, 1):
76                 status = 303
77             else:
78                 status = 302
79         else:
80             status = int(status)
81             if status < 300 or status > 399:
82                 raise ValueError("status must be between 300 and 399.")
83        
84         self.status = status
85         Exception.__init__(self, abs_urls, status)
86    
87     def set_response(self):
88         import cherrypy
89         response = cherrypy.response
90         response.status = status = self.status
91        
92         if status in (300, 301, 302, 303, 307):
93             response.headers['Content-Type'] = "text/html"
94             # "The ... URI SHOULD be given by the Location field
95             # in the response."
96             response.headers['Location'] = self.urls[0]
97            
98             # "Unless the request method was HEAD, the entity of the response
99             # SHOULD contain a short hypertext note with a hyperlink to the
100             # new URI(s)."
101             msg = {300: "This resource can be found at <a href='%s'>%s</a>.",
102                    301: "This resource has permanently moved to <a href='%s'>%s</a>.",
103                    302: "This resource resides temporarily at <a href='%s'>%s</a>.",
104                    303: "This resource can be found at <a href='%s'>%s</a>.",
105                    307: "This resource has moved temporarily to <a href='%s'>%s</a>.",
106                    }[status]
107             response.body = "<br />\n".join([msg % (u, u) for u in self.urls])
108         elif status == 304:
109             # Not Modified.
110             # "The response MUST include the following header fields:
111             # Date, unless its omission is required by section 14.18.1"
112             # The "Date" header should have been set in Request.__init__
113            
114             # "...the response SHOULD NOT include other entity-headers.
115             for key in ('Allow', 'Content-Encoding', 'Content-Language',
116                         'Content-Length', 'Content-Location', 'Content-MD5',
117                         'Content-Range', 'Content-Type', 'Expires',
118                         'Last-Modified'):
119                 if key in response.headers:
120                     del response.headers[key]
121            
122             # "The 304 response MUST NOT contain a message-body."
123             response.body = None
124         elif status == 305:
125             # Use Proxy.
126             # self.urls[0] should be the URI of the proxy.
127             response.headers['Location'] = self.urls[0]
128             response.body = None
129         else:
130             raise ValueError("The %s status code is unknown." % status)
131    
132     def __call__(self):
133         # Allow the exception to be used as a request.handler.
134         raise self
135
136
137 class HTTPError(Exception):
138     """ Exception used to return an HTTP error code to the client.
139         This exception will automatically set the response status and body.
140         
141         A custom message (a long description to display in the browser)
142         can be provided in place of the default.
143     """
144    
145     def __init__(self, status=500, message=None):
146         self.status = status = int(status)
147         if status < 400 or status > 599:
148             raise ValueError("status must be between 400 and 599.")
149         self.message = message
150         Exception.__init__(self, status, message)
151    
152     def set_response(self):
153         """Set cherrypy.response status, headers, and body."""
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         for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After",
161                     "Vary", "Content-Encoding", "Content-Length", "Expires",
162                     "Content-Location", "Content-MD5", "Last-Modified"]:
163             if response.headers.has_key(key):
164                 del response.headers[key]
165        
166         if self.status != 416:
167             # A server sending a response with status code 416 (Requested
168             # range not satisfiable) SHOULD include a Content-Range field
169             # with a byte-range- resp-spec of "*". The instance-length
170             # specifies the current length of the selected resource.
171             # A response with status code 206 (Partial Content) MUST NOT
172             # include a Content-Range field with a byte-range- resp-spec of "*".
173             if response.headers.has_key("Content-Range"):
174                 del response.headers["Content-Range"]
175        
176         # In all cases, finalize will be called after this method,
177         # so don't bother cleaning up response values here.
178         response.status = self.status
179         tb = None
180         if cherrypy.config.get('show_tracebacks', False):
181             tb = format_exc()
182         content = get_error_page(self.status, traceback=tb,
183                                  message=self.message)
184         response.body = content
185         response.headers['Content-Length'] = len(content)
186         response.headers['Content-Type'] = "text/html"
187        
188         _be_ie_unfriendly(self.status)
189    
190     def __call__(self):
191         # Allow the exception to be used as a request.handler.
192         raise self
193
194
195 class NotFound(HTTPError):
196     """ Happens when a URL couldn't be mapped to any class.method """
197    
198     def __init__(self, path=None):
199         if path is None:
200             import cherrypy
201             path = cherrypy.request.path
202         self.args = (path,)
203         HTTPError.__init__(self, 404, "The path %r was not found." % path)
204
205
206 class TimeoutError(Exception):
207     """Exception raised when Response.timed_out is detected."""
208     pass
209
210
211 _HTTPErrorTemplate = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
212 <html>
213 <head>
214     <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
215     <title>%(status)s</title>
216     <style type="text/css">
217     #powered_by {
218         margin-top: 20px;
219         border-top: 2px solid black;
220         font-style: italic;
221     }
222
223     #traceback {
224         color: red;
225     }
226     </style>
227 </head>
228     <body>
229         <h2>%(status)s</h2>
230         <p>%(message)s</p>
231         <pre id="traceback">%(traceback)s</pre>
232     <div id="powered_by">
233     <span>Powered by <a href="CherryPy">http://www.cherrypy.org">CherryPy %(version)s</a></span>
234     </div>
235     </body>
236 </html>
237 '''
238
239 def get_error_page(status, **kwargs):
240     """Return an HTML page, containing a pretty error response.
241     
242     status should be an int or a str.
243     kwargs will be interpolated into the page template.
244     """
245     import cherrypy
246    
247     try:
248         code, reason, message = _http.valid_status(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] = _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" % (_exc_info()[1]))
279             kwargs['message'] = m
280    
281     return template % kwargs
282
283
284 _ie_friendly_error_sizes = {
285     400: 512, 403: 256, 404: 512, 405: 256,
286     406: 512, 408: 512, 409: 512, 410: 256,
287     500: 512, 501: 512, 505: 512,
288     }
289
290
291 def _be_ie_unfriendly(status):
292     import cherrypy
293     response = cherrypy.response
294    
295     # For some statuses, Internet Explorer 5+ shows "friendly error
296     # messages" instead of our response.body if the body is smaller
297     # than a given size. Fix this by returning a body over that size
298     # (by adding whitespace).
299     # See http://support.microsoft.com/kb/q218155/
300     s = _ie_friendly_error_sizes.get(status, 0)
301     if s:
302         s += 1
303         # Since we are issuing an HTTP error status, we assume that
304         # the entity is short, and we should just collapse it.
305         content = response.collapse_body()
306         l = len(content)
307         if l and l < s:
308             # IN ADDITION: the response must be written to IE
309             # in one chunk or it will still get replaced! Bah.
310             content = content + (" " * (s - l))
311         response.body = content
312         response.headers['Content-Length'] = len(content)
313
314
315 def format_exc(exc=None):
316     """format_exc(exc=None) -> exc (or sys.exc_info if None), formatted."""
317     if exc is None:
318         exc = _exc_info()
319     if exc == (None, None, None):
320         return ""
321     import traceback
322     return "".join(traceback.format_exception(*exc))
323
324 def bare_error(extrabody=None):
325     """Produce status, headers, body for a critical error.
326     
327     Returns a triple without calling any other questionable functions,
328     so it should be as error-free as possible. Call it from an HTTP server
329     if you get errors outside of the request.
330     
331     If extrabody is None, a friendly but rather unhelpful error message
332     is set in the body. If extrabody is a string, it will be appended
333     as-is to the body.
334     """
335    
336     # The whole point of this function is to be a last line-of-defense
337     # in handling errors. That is, it must not raise any errors itself;
338     # it cannot be allowed to fail. Therefore, don't add to it!
339     # In particular, don't call any other CP functions.
340    
341     body = "Unrecoverable error in the server."
342     if extrabody is not None:
343         body += "\n" + extrabody
344    
345     return ("500 Internal Server Error",
346             [('Content-Type', 'text/plain'),
347              ('Content-Length', str(len(body)))],
348             [body])
349
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets