1
"""Error classes for CherryPy."""
1
"""Exception classes for CherryPy.
3
CherryPy provides (and uses) exceptions for declaring that the HTTP response
4
should be a status other than the default "200 OK". You can ``raise`` them like
5
normal Python exceptions. You can also call them and they will raise themselves;
6
this means you can set an :class:`HTTPError<cherrypy._cperror.HTTPError>`
7
or :class:`HTTPRedirect<cherrypy._cperror.HTTPRedirect>` as the
8
:attr:`request.handler<cherrypy._cprequest.Request.handler>`.
15
When you GET a resource and are redirected by the server to another Location,
16
there's generally no problem since GET is both a "safe method" (there should
17
be no side-effects) and an "idempotent method" (multiple calls are no different
20
POST, however, is neither safe nor idempotent--if you
21
charge a credit card, you don't want to be charged twice by a redirect!
23
For this reason, *none* of the 3xx responses permit a user-agent (browser) to
24
resubmit a POST on redirection without first confirming the action with the user:
26
===== ================================= ===========
27
300 Multiple Choices Confirm with the user
28
301 Moved Permanently Confirm with the user
29
302 Found (Object moved temporarily) Confirm with the user
30
303 See Other GET the new URI--no confirmation
31
304 Not modified (for conditional GET only--POST should not raise this error)
32
305 Use Proxy Confirm with the user
33
307 Temporary Redirect Confirm with the user
34
===== ================================= ===========
36
However, browsers have historically implemented these restrictions poorly;
37
in particular, many browsers do not force the user to confirm 301, 302
38
or 307 when redirecting POST. For this reason, CherryPy defaults to 303,
39
which most user-agents appear to have implemented correctly. Therefore, if
40
you raise HTTPRedirect for a POST request, the user-agent will most likely
41
attempt to GET the new URI (without asking for confirmation from the user).
42
We realize this is confusing for developers, but it's the safest thing we
43
could do. You are of course free to raise ``HTTPRedirect(uri, status=302)``
44
or any other 3xx status if you know what you're doing, but given the
45
environment, we couldn't let any of those be the default.
50
.. image:: /refman/cperrors.gif
52
Anticipated HTTP responses
53
--------------------------
55
The 'error_page' config namespace can be used to provide custom HTML output for
56
expected responses (like 404 Not Found). Supply a filename from which the output
57
will be read. The contents will be interpolated with the values %(status)s,
58
%(message)s, %(traceback)s, and %(version)s using plain old Python
59
`string formatting <http://www.python.org/doc/2.6.4/library/stdtypes.html#string-formatting-operations>`_.
63
_cp_config = {'error_page.404': os.path.join(localDir, "static/index.html")}
66
Beginning in version 3.1, you may also provide a function or other callable as
67
an error_page entry. It will be passed the same status, message, traceback and
68
version arguments that are interpolated into templates::
70
def error_page_402(status, message, traceback, version):
71
return "Error %s - Well, I'm very sorry but you haven't paid!" % status
72
cherrypy.config.update({'error_page.402': error_page_402})
74
Also in 3.1, in addition to the numbered error codes, you may also supply
75
"error_page.default" to handle all codes which do not have their own error_page entry.
82
CherryPy also has a generic error handling mechanism: whenever an unanticipated
83
error occurs in your code, it will call
84
:func:`Request.error_response<cherrypy._cprequest.Request.error_response>` to set
85
the response status, headers, and body. By default, this is the same output as
86
:class:`HTTPError(500) <cherrypy._cperror.HTTPError>`. If you want to provide
87
some other behavior, you generally replace "request.error_response".
89
Here is some sample code that shows how to display a custom error message and
90
send an e-mail containing the error::
92
from cherrypy import _cperror
95
cherrypy.response.status = 500
96
cherrypy.response.body = ["<html><body>Sorry, an error occured</body></html>"]
97
sendMail('error@domain.com', 'Error in your web app', _cperror.format_exc())
100
_cp_config = {'request.error_response': handle_error}
103
Note that you have to explicitly set :attr:`response.body <cherrypy._cprequest.Response.body>`
104
and not simply return an error message as a result.
3
107
from cgi import escape as _escape
4
108
from sys import exc_info as _exc_info
5
109
from traceback import format_exception as _format_exception
6
from urlparse import urljoin as _urljoin
7
from cherrypy.lib import http as _http
110
from cherrypy._cpcompat import basestring, bytestr, iteritems, ntob, tonative, urljoin as _urljoin
111
from cherrypy.lib import httputil as _httputil
10
114
class CherryPyException(Exception):
115
"""A base class for CherryPy exceptions."""
47
154
class HTTPRedirect(CherryPyException):
48
155
"""Exception raised when the request should be redirected.
157
This exception will force a HTTP redirect to the URL or URL's you give it.
50
158
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.
159
e.g., HTTPRedirect(newUrl). Multiple URLs are allowed in a list.
160
If a URL is absolute, it will be used as-is. If it is relative, it is
161
assumed to be relative to the current cherrypy.request.path_info.
163
If one of the provided URL is a unicode object, it will be encoded
164
using the default encoding or the one passed in parameter.
166
There are multiple types of redirect, from which you can select via the
167
``status`` argument. If you do not provide a ``status`` arg, it defaults to
168
303 (or 302 if responding with HTTP/1.0).
172
raise cherrypy.HTTPRedirect("")
173
raise cherrypy.HTTPRedirect("/abs/path", 307)
174
raise cherrypy.HTTPRedirect(["path1", "path2?a=1&b=2"], 301)
176
See :ref:`redirectingpost` for additional caveats.
56
def __init__(self, urls, status=None):
180
"""The integer HTTP status code to emit."""
183
"""The list of URL's to emit."""
186
"""The encoding when passed urls are not native strings"""
188
def __init__(self, urls, status=None, encoding=None):
58
request = cherrypy.request
190
request = cherrypy.serving.request
60
192
if isinstance(urls, basestring):
197
url = tonative(url, encoding or self.encoding)
65
199
# Note that urljoin will "do the right thing" whether url is:
66
200
# 1. a complete URL with host (e.g. "http://www.example.com/test")
67
201
# 2. a URL relative to root (e.g. "/dummy")
171
306
# specifies the current length of the selected resource.
172
307
# A response with status code 206 (Partial Content) MUST NOT
173
308
# include a Content-Range field with a byte-range- resp-spec of "*".
174
if respheaders.has_key("Content-Range"):
309
if "Content-Range" in respheaders:
175
310
del respheaders["Content-Range"]
178
313
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.
314
"""Exception used to return an HTTP error code (4xx-5xx) to the client.
316
This exception can be used to automatically send a response using a http status
317
code, with an appropriate error page. It takes an optional
318
``status`` argument (which must be between 400 and 599); it defaults to 500
319
("Internal Server Error"). It also takes an optional ``message`` argument,
320
which will be returned in the response body. See
321
`RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4>`_
322
for a complete list of available error codes and when to use them.
182
A custom message (a long description to display in the browser)
183
can be provided in place of the default.
326
raise cherrypy.HTTPError(403)
327
raise cherrypy.HTTPError("403 Forbidden", "You are not allowed to access this resource.")
331
"""The HTTP status code. May be of type int or str (with a Reason-Phrase)."""
334
"""The integer HTTP status code."""
337
"""The HTTP Reason-Phrase string."""
186
339
def __init__(self, status=500, message=None):
187
self.status = status = int(status)
188
if status < 400 or status > 599:
342
self.code, self.reason, defaultmsg = _httputil.valid_status(status)
344
raise self.__class__(500, _exc_info()[1].args[0])
346
if self.code < 400 or self.code > 599:
189
347
raise ValueError("status must be between 400 and 599.")
190
349
# See http://www.python.org/dev/peps/pep-0352/
191
350
# self.message = message
192
self._message = message
351
self._message = message or defaultmsg
193
352
CherryPyException.__init__(self, status, message)
195
354
def set_response(self):
203
response = cherrypy.response
362
response = cherrypy.serving.response
205
clean_headers(self.status)
364
clean_headers(self.code)
207
366
# In all cases, finalize will be called after this method,
208
367
# so don't bother cleaning up response values here.
209
368
response.status = self.status
211
if cherrypy.request.show_tracebacks:
370
if cherrypy.serving.request.show_tracebacks:
212
371
tb = format_exc()
213
response.headers['Content-Type'] = "text/html"
372
response.headers['Content-Type'] = "text/html;charset=utf-8"
373
response.headers.pop('Content-Length', None)
215
content = self.get_error_page(self.status, traceback=tb,
216
message=self._message)
375
content = ntob(self.get_error_page(self.status, traceback=tb,
376
message=self._message), 'utf-8')
217
377
response.body = content
218
response.headers['Content-Length'] = len(content)
220
_be_ie_unfriendly(self.status)
379
_be_ie_unfriendly(self.code)
222
381
def get_error_page(self, *args, **kwargs):
223
382
return get_error_page(*args, **kwargs)
291
455
if kwargs.get('version') is None:
292
456
kwargs['version'] = cherrypy.__version__
294
for k, v in kwargs.iteritems():
458
for k, v in iteritems(kwargs):
298
462
kwargs[k] = _escape(kwargs[k])
300
464
# Use a custom template or callable for the error page?
301
pages = cherrypy.request.error_page
465
pages = cherrypy.serving.request.error_page
302
466
error_page = pages.get(code) or pages.get('default')
305
if callable(error_page):
469
if hasattr(error_page, '__call__'):
306
470
return error_page(**kwargs)
308
return file(error_page, 'rb').read() % kwargs
472
data = open(error_page, 'rb').read()
473
return tonative(data) % kwargs
310
475
e = _format_exception(*_exc_info())[-1]
311
476
m = kwargs['message']
344
509
# IN ADDITION: the response must be written to IE
345
510
# in one chunk or it will still get replaced! Bah.
346
content = content + (" " * (s - l))
511
content = content + (ntob(" ") * (s - l))
347
512
response.body = content
348
response.headers['Content-Length'] = len(content)
513
response.headers['Content-Length'] = str(len(content))
351
516
def format_exc(exc=None):
352
517
"""Return exc (or sys.exc_info if None), formatted."""
355
if exc == (None, None, None):
358
return "".join(traceback.format_exception(*exc))
521
if exc == (None, None, None):
524
return "".join(traceback.format_exception(*exc))
360
528
def bare_error(extrabody=None):
361
529
"""Produce status, headers, body for a critical error.
374
542
# it cannot be allowed to fail. Therefore, don't add to it!
375
543
# In particular, don't call any other CP functions.
377
body = "Unrecoverable error in the server."
545
body = ntob("Unrecoverable error in the server.")
378
546
if extrabody is not None:
379
body += "\n" + extrabody
547
if not isinstance(extrabody, bytestr):
548
extrabody = extrabody.encode('utf-8')
549
body += ntob("\n") + extrabody
381
return ("500 Internal Server Error",
382
[('Content-Type', 'text/plain'),
383
('Content-Length', str(len(body)))],
551
return (ntob("500 Internal Server Error"),
552
[(ntob('Content-Type'), ntob('text/plain')),
553
(ntob('Content-Length'), ntob(str(len(body)),'ISO-8859-1'))],