1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) |
---|
2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php |
---|
3 | # (c) 2005 Ian Bicking, Clark C. Evans and contributors |
---|
4 | # This module is part of the Python Paste Project and is released under |
---|
5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php |
---|
6 | # Some of this code was funded by http://prometheusresearch.com |
---|
7 | """ |
---|
8 | HTTP Exception Middleware |
---|
9 | |
---|
10 | This module processes Python exceptions that relate to HTTP exceptions |
---|
11 | by defining a set of exceptions, all subclasses of HTTPException, and a |
---|
12 | request handler (`middleware`) that catches these exceptions and turns |
---|
13 | them into proper responses. |
---|
14 | |
---|
15 | This module defines exceptions according to RFC 2068 [1]_ : codes with |
---|
16 | 100-300 are not really errors; 400's are client errors, and 500's are |
---|
17 | server errors. According to the WSGI specification [2]_ , the application |
---|
18 | can call ``start_response`` more then once only under two conditions: |
---|
19 | (a) the response has not yet been sent, or (b) if the second and |
---|
20 | subsequent invocations of ``start_response`` have a valid ``exc_info`` |
---|
21 | argument obtained from ``sys.exc_info()``. The WSGI specification then |
---|
22 | requires the server or gateway to handle the case where content has been |
---|
23 | sent and then an exception was encountered. |
---|
24 | |
---|
25 | Exceptions in the 5xx range and those raised after ``start_response`` |
---|
26 | has been called are treated as serious errors and the ``exc_info`` is |
---|
27 | filled-in with information needed for a lower level module to generate a |
---|
28 | stack trace and log information. |
---|
29 | |
---|
30 | Exception |
---|
31 | HTTPException |
---|
32 | HTTPRedirection |
---|
33 | * 300 - HTTPMultipleChoices |
---|
34 | * 301 - HTTPMovedPermanently |
---|
35 | * 302 - HTTPFound |
---|
36 | * 303 - HTTPSeeOther |
---|
37 | * 304 - HTTPNotModified |
---|
38 | * 305 - HTTPUseProxy |
---|
39 | * 306 - Unused (not implemented, obviously) |
---|
40 | * 307 - HTTPTemporaryRedirect |
---|
41 | HTTPError |
---|
42 | HTTPClientError |
---|
43 | * 400 - HTTPBadRequest |
---|
44 | * 401 - HTTPUnauthorized |
---|
45 | * 402 - HTTPPaymentRequired |
---|
46 | * 403 - HTTPForbidden |
---|
47 | * 404 - HTTPNotFound |
---|
48 | * 405 - HTTPMethodNotAllowed |
---|
49 | * 406 - HTTPNotAcceptable |
---|
50 | * 407 - HTTPProxyAuthenticationRequired |
---|
51 | * 408 - HTTPRequestTimeout |
---|
52 | * 409 - HTTPConfict |
---|
53 | * 410 - HTTPGone |
---|
54 | * 411 - HTTPLengthRequired |
---|
55 | * 412 - HTTPPreconditionFailed |
---|
56 | * 413 - HTTPRequestEntityTooLarge |
---|
57 | * 414 - HTTPRequestURITooLong |
---|
58 | * 415 - HTTPUnsupportedMediaType |
---|
59 | * 416 - HTTPRequestRangeNotSatisfiable |
---|
60 | * 417 - HTTPExpectationFailed |
---|
61 | HTTPServerError |
---|
62 | * 500 - HTTPInternalServerError |
---|
63 | * 501 - HTTPNotImplemented |
---|
64 | * 502 - HTTPBadGateway |
---|
65 | * 503 - HTTPServiceUnavailable |
---|
66 | * 504 - HTTPGatewayTimeout |
---|
67 | * 505 - HTTPVersionNotSupported |
---|
68 | |
---|
69 | References: |
---|
70 | |
---|
71 | .. [1] http://www.python.org/peps/pep-0333.html#error-handling |
---|
72 | .. [2] http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5 |
---|
73 | |
---|
74 | """ |
---|
75 | |
---|
76 | import types |
---|
77 | from paste.wsgilib import catch_errors_app |
---|
78 | from paste.response import has_header, header_value, replace_header |
---|
79 | from paste.request import resolve_relative_url |
---|
80 | from paste.util.quoting import strip_html, html_quote, no_quote |
---|
81 | |
---|
82 | SERVER_NAME = 'WSGI Server' |
---|
83 | TEMPLATE = """\ |
---|
84 | <html>\r |
---|
85 | <head><title>%(title)s</title></head>\r |
---|
86 | <body>\r |
---|
87 | <h1>%(title)s</h1>\r |
---|
88 | <p>%(body)s</p>\r |
---|
89 | <hr noshade>\r |
---|
90 | <div align="right">%(server)s</div>\r |
---|
91 | </body>\r |
---|
92 | </html>\r |
---|
93 | """ |
---|
94 | |
---|
95 | class HTTPException(Exception): |
---|
96 | """ |
---|
97 | the HTTP exception base class |
---|
98 | |
---|
99 | This encapsulates an HTTP response that interrupts normal application |
---|
100 | flow; but one which is not necessarly an error condition. For |
---|
101 | example, codes in the 300's are exceptions in that they interrupt |
---|
102 | normal processing; however, they are not considered errors. |
---|
103 | |
---|
104 | This class is complicated by 4 factors: |
---|
105 | |
---|
106 | 1. The content given to the exception may either be plain-text or |
---|
107 | as html-text. |
---|
108 | |
---|
109 | 2. The template may want to have string-substitutions taken from |
---|
110 | the current ``environ`` or values from incoming headers. This |
---|
111 | is especially troublesome due to case sensitivity. |
---|
112 | |
---|
113 | 3. The final output may either be text/plain or text/html |
---|
114 | mime-type as requested by the client application. |
---|
115 | |
---|
116 | 4. Each exception has a default explanation, but those who |
---|
117 | raise exceptions may want to provide additional detail. |
---|
118 | |
---|
119 | Attributes: |
---|
120 | |
---|
121 | ``code`` |
---|
122 | the HTTP status code for the exception |
---|
123 | |
---|
124 | ``title`` |
---|
125 | remainder of the status line (stuff after the code) |
---|
126 | |
---|
127 | ``explanation`` |
---|
128 | a plain-text explanation of the error message that is |
---|
129 | not subject to environment or header substitutions; |
---|
130 | it is accessible in the template via %(explanation)s |
---|
131 | |
---|
132 | ``detail`` |
---|
133 | a plain-text message customization that is not subject |
---|
134 | to environment or header substitutions; accessible in |
---|
135 | the template via %(detail)s |
---|
136 | |
---|
137 | ``template`` |
---|
138 | a content fragment (in HTML) used for environment and |
---|
139 | header substitution; the default template includes both |
---|
140 | the explanation and further detail provided in the |
---|
141 | message |
---|
142 | |
---|
143 | ``required_headers`` |
---|
144 | a sequence of headers which are required for proper |
---|
145 | construction of the exception |
---|
146 | |
---|
147 | Parameters: |
---|
148 | |
---|
149 | ``detail`` |
---|
150 | a plain-text override of the default ``detail`` |
---|
151 | |
---|
152 | ``headers`` |
---|
153 | a list of (k,v) header pairs |
---|
154 | |
---|
155 | ``comment`` |
---|
156 | a plain-text additional information which is |
---|
157 | usually stripped/hidden for end-users |
---|
158 | |
---|
159 | To override the template (which is HTML content) or the plain-text |
---|
160 | explanation, one must subclass the given exception; or customize it |
---|
161 | after it has been created. This particular breakdown of a message |
---|
162 | into explanation, detail and template allows both the creation of |
---|
163 | plain-text and html messages for various clients as well as |
---|
164 | error-free substitution of environment variables and headers. |
---|
165 | """ |
---|
166 | |
---|
167 | code = None |
---|
168 | title = None |
---|
169 | explanation = '' |
---|
170 | detail = '' |
---|
171 | comment = '' |
---|
172 | template = "%(explanation)s\r\n<br/>%(detail)s\r\n<!-- %(comment)s -->" |
---|
173 | required_headers = () |
---|
174 | |
---|
175 | def __init__(self, detail=None, headers=None, comment=None): |
---|
176 | assert self.code, "Do not directly instantiate abstract exceptions." |
---|
177 | assert isinstance(headers, (type(None), list)), ( |
---|
178 | "headers must be None or a list: %r" |
---|
179 | % headers) |
---|
180 | assert isinstance(detail, (type(None), basestring)), ( |
---|
181 | "detail must be None or a string: %r" % detail) |
---|
182 | assert isinstance(comment, (type(None), basestring)), ( |
---|
183 | "comment must be None or a string: %r" % comment) |
---|
184 | self.headers = headers or tuple() |
---|
185 | for req in self.required_headers: |
---|
186 | assert headers and has_header(headers, req), ( |
---|
187 | "Exception %s must be passed the header %r " |
---|
188 | "(got headers: %r)" |
---|
189 | % (self.__class__.__name__, req, headers)) |
---|
190 | if detail is not None: |
---|
191 | self.detail = detail |
---|
192 | if comment is not None: |
---|
193 | self.comment = comment |
---|
194 | Exception.__init__(self,"%s %s\n%s\n%s\n" % ( |
---|
195 | self.code, self.title, self.explanation, self.detail)) |
---|
196 | |
---|
197 | def make_body(self, environ, template, escfunc, comment_escfunc=None): |
---|
198 | comment_escfunc = comment_escfunc or escfunc |
---|
199 | args = {'explanation': escfunc(self.explanation), |
---|
200 | 'detail': escfunc(self.detail), |
---|
201 | 'comment': comment_escfunc(self.comment)} |
---|
202 | if HTTPException.template == self.template: |
---|
203 | return template % args |
---|
204 | for (k, v) in environ.items(): |
---|
205 | args[k] = escfunc(v) |
---|
206 | if self.headers: |
---|
207 | for (k, v) in self.headers: |
---|
208 | args[k.lower()] = escfunc(v) |
---|
209 | return template % args |
---|
210 | |
---|
211 | def plain(self, environ): |
---|
212 | """ text/plain representation of the exception """ |
---|
213 | body = self.make_body(environ, strip_html(self.template), no_quote) |
---|
214 | return ('%s %s\r\n%s\r\n' % (self.code, self.title, body)) |
---|
215 | |
---|
216 | def html(self, environ): |
---|
217 | """ text/html representation of the exception """ |
---|
218 | body = self.make_body(environ, self.template, html_quote, no_quote) |
---|
219 | return TEMPLATE % { |
---|
220 | 'title': self.title, |
---|
221 | 'code': self.code, |
---|
222 | 'server': SERVER_NAME, |
---|
223 | 'body': body } |
---|
224 | |
---|
225 | def prepare_content(self, environ): |
---|
226 | if self.headers: |
---|
227 | headers = list(self.headers) |
---|
228 | else: |
---|
229 | headers = [] |
---|
230 | if 'html' in environ.get('HTTP_ACCEPT','') or \ |
---|
231 | '*/*' in environ.get('HTTP_ACCEPT',''): |
---|
232 | replace_header(headers, 'content-type', 'text/html') |
---|
233 | content = self.html(environ) |
---|
234 | else: |
---|
235 | replace_header(headers, 'content-type', 'text/plain') |
---|
236 | content = self.plain(environ) |
---|
237 | if isinstance(content, unicode): |
---|
238 | content = content.encode('utf8') |
---|
239 | cur_content_type = ( |
---|
240 | header_value(headers, 'content-type') |
---|
241 | or 'text/html') |
---|
242 | replace_header( |
---|
243 | headers, 'content-type', |
---|
244 | cur_content_type + '; charset=utf8') |
---|
245 | return headers, content |
---|
246 | |
---|
247 | def response(self, environ): |
---|
248 | from paste.wsgiwrappers import WSGIResponse |
---|
249 | headers, content = self.prepare_content(environ) |
---|
250 | resp = WSGIResponse(code=self.code, content=content) |
---|
251 | resp.headers = resp.headers.fromlist(headers) |
---|
252 | return resp |
---|
253 | |
---|
254 | def wsgi_application(self, environ, start_response, exc_info=None): |
---|
255 | """ |
---|
256 | This exception as a WSGI application |
---|
257 | """ |
---|
258 | headers, content = self.prepare_content(environ) |
---|
259 | start_response('%s %s' % (self.code, self.title), |
---|
260 | headers, |
---|
261 | exc_info) |
---|
262 | return [content] |
---|
263 | |
---|
264 | __call__ = wsgi_application |
---|
265 | |
---|
266 | def __repr__(self): |
---|
267 | return '<%s %s; code=%s>' % (self.__class__.__name__, |
---|
268 | self.title, self.code) |
---|
269 | |
---|
270 | class HTTPError(HTTPException): |
---|
271 | """ |
---|
272 | base class for status codes in the 400's and 500's |
---|
273 | |
---|
274 | This is an exception which indicates that an error has occurred, |
---|
275 | and that any work in progress should not be committed. These are |
---|
276 | typically results in the 400's and 500's. |
---|
277 | """ |
---|
278 | |
---|
279 | # |
---|
280 | # 3xx Redirection |
---|
281 | # |
---|
282 | # This class of status code indicates that further action needs to be |
---|
283 | # taken by the user agent in order to fulfill the request. The action |
---|
284 | # required MAY be carried out by the user agent without interaction with |
---|
285 | # the user if and only if the method used in the second request is GET or |
---|
286 | # HEAD. A client SHOULD detect infinite redirection loops, since such |
---|
287 | # loops generate network traffic for each redirection. |
---|
288 | # |
---|
289 | |
---|
290 | class HTTPRedirection(HTTPException): |
---|
291 | """ |
---|
292 | base class for 300's status code (redirections) |
---|
293 | |
---|
294 | This is an abstract base class for 3xx redirection. It indicates |
---|
295 | that further action needs to be taken by the user agent in order |
---|
296 | to fulfill the request. It does not necessarly signal an error |
---|
297 | condition. |
---|
298 | """ |
---|
299 | |
---|
300 | class _HTTPMove(HTTPRedirection): |
---|
301 | """ |
---|
302 | redirections which require a Location field |
---|
303 | |
---|
304 | Since a 'Location' header is a required attribute of 301, 302, 303, |
---|
305 | 305 and 307 (but not 304), this base class provides the mechanics to |
---|
306 | make this easy. While this has the same parameters as HTTPException, |
---|
307 | if a location is not provided in the headers; it is assumed that the |
---|
308 | detail _is_ the location (this for backward compatibility, otherwise |
---|
309 | we'd add a new attribute). |
---|
310 | """ |
---|
311 | required_headers = ('location',) |
---|
312 | explanation = 'The resource has been moved to' |
---|
313 | template = ( |
---|
314 | '%(explanation)s <a href="%(location)s">%(location)s</a>;\r\n' |
---|
315 | 'you should be redirected automatically.\r\n' |
---|
316 | '%(detail)s\r\n<!-- %(comment)s -->') |
---|
317 | |
---|
318 | def __init__(self, detail=None, headers=None, comment=None): |
---|
319 | assert isinstance(headers, (type(None), list)) |
---|
320 | headers = headers or [] |
---|
321 | location = header_value(headers,'location') |
---|
322 | if not location: |
---|
323 | location = detail |
---|
324 | detail = '' |
---|
325 | headers.append(('location', location)) |
---|
326 | assert location, ("HTTPRedirection specified neither a " |
---|
327 | "location in the headers nor did it " |
---|
328 | "provide a detail argument.") |
---|
329 | HTTPRedirection.__init__(self, location, headers, comment) |
---|
330 | if detail is not None: |
---|
331 | self.detail = detail |
---|
332 | |
---|
333 | def relative_redirect(cls, dest_uri, environ, detail=None, headers=None, comment=None): |
---|
334 | """ |
---|
335 | Create a redirect object with the dest_uri, which may be relative, |
---|
336 | considering it relative to the uri implied by the given environ. |
---|
337 | """ |
---|
338 | location = resolve_relative_url(dest_uri, environ) |
---|
339 | headers = headers or [] |
---|
340 | headers.append(('Location', location)) |
---|
341 | return cls(detail=detail, headers=headers, comment=comment) |
---|
342 | |
---|
343 | relative_redirect = classmethod(relative_redirect) |
---|
344 | |
---|
345 | def location(self): |
---|
346 | for name, value in self.headers: |
---|
347 | if name.lower() == 'location': |
---|
348 | return value |
---|
349 | else: |
---|
350 | raise KeyError("No location set for %s" % self) |
---|
351 | |
---|
352 | class HTTPMultipleChoices(_HTTPMove): |
---|
353 | code = 300 |
---|
354 | title = 'Multiple Choices' |
---|
355 | |
---|
356 | class HTTPMovedPermanently(_HTTPMove): |
---|
357 | code = 301 |
---|
358 | title = 'Moved Permanently' |
---|
359 | |
---|
360 | class HTTPFound(_HTTPMove): |
---|
361 | code = 302 |
---|
362 | title = 'Found' |
---|
363 | explanation = 'The resource was found at' |
---|
364 | |
---|
365 | # This one is safe after a POST (the redirected location will be |
---|
366 | # retrieved with GET): |
---|
367 | class HTTPSeeOther(_HTTPMove): |
---|
368 | code = 303 |
---|
369 | title = 'See Other' |
---|
370 | |
---|
371 | class HTTPNotModified(HTTPRedirection): |
---|
372 | # @@: but not always (HTTP section 14.18.1)...? |
---|
373 | # @@: Removed 'date' requirement, as its not required for an ETag |
---|
374 | # @@: FIXME: This should require either an ETag or a date header |
---|
375 | code = 304 |
---|
376 | title = 'Not Modified' |
---|
377 | message = '' |
---|
378 | # @@: should include date header, optionally other headers |
---|
379 | # @@: should not return a content body |
---|
380 | def plain(self, environ): |
---|
381 | return '' |
---|
382 | def html(self, environ): |
---|
383 | """ text/html representation of the exception """ |
---|
384 | return '' |
---|
385 | |
---|
386 | class HTTPUseProxy(_HTTPMove): |
---|
387 | # @@: OK, not a move, but looks a little like one |
---|
388 | code = 305 |
---|
389 | title = 'Use Proxy' |
---|
390 | explanation = ( |
---|
391 | 'The resource must be accessed through a proxy ' |
---|
392 | 'located at') |
---|
393 | |
---|
394 | class HTTPTemporaryRedirect(_HTTPMove): |
---|
395 | code = 307 |
---|
396 | title = 'Temporary Redirect' |
---|
397 | |
---|
398 | # |
---|
399 | # 4xx Client Error |
---|
400 | # |
---|
401 | # The 4xx class of status code is intended for cases in which the client |
---|
402 | # seems to have erred. Except when responding to a HEAD request, the |
---|
403 | # server SHOULD include an entity containing an explanation of the error |
---|
404 | # situation, and whether it is a temporary or permanent condition. These |
---|
405 | # status codes are applicable to any request method. User agents SHOULD |
---|
406 | # display any included entity to the user. |
---|
407 | # |
---|
408 | |
---|
409 | class HTTPClientError(HTTPError): |
---|
410 | """ |
---|
411 | base class for the 400's, where the client is in-error |
---|
412 | |
---|
413 | This is an error condition in which the client is presumed to be |
---|
414 | in-error. This is an expected problem, and thus is not considered |
---|
415 | a bug. A server-side traceback is not warranted. Unless specialized, |
---|
416 | this is a '400 Bad Request' |
---|
417 | """ |
---|
418 | code = 400 |
---|
419 | title = 'Bad Request' |
---|
420 | explanation = ('The server could not comply with the request since\r\n' |
---|
421 | 'it is either malformed or otherwise incorrect.\r\n') |
---|
422 | |
---|
423 | class HTTPBadRequest(HTTPClientError): |
---|
424 | pass |
---|
425 | |
---|
426 | class HTTPUnauthorized(HTTPClientError): |
---|
427 | code = 401 |
---|
428 | title = 'Unauthorized' |
---|
429 | explanation = ( |
---|
430 | 'This server could not verify that you are authorized to\r\n' |
---|
431 | 'access the document you requested. Either you supplied the\r\n' |
---|
432 | 'wrong credentials (e.g., bad password), or your browser\r\n' |
---|
433 | 'does not understand how to supply the credentials required.\r\n') |
---|
434 | |
---|
435 | class HTTPPaymentRequired(HTTPClientError): |
---|
436 | code = 402 |
---|
437 | title = 'Payment Required' |
---|
438 | explanation = ('Access was denied for financial reasons.') |
---|
439 | |
---|
440 | class HTTPForbidden(HTTPClientError): |
---|
441 | code = 403 |
---|
442 | title = 'Forbidden' |
---|
443 | explanation = ('Access was denied to this resource.') |
---|
444 | |
---|
445 | class HTTPNotFound(HTTPClientError): |
---|
446 | code = 404 |
---|
447 | title = 'Not Found' |
---|
448 | explanation = ('The resource could not be found.') |
---|
449 | |
---|
450 | class HTTPMethodNotAllowed(HTTPClientError): |
---|
451 | required_headers = ('allow',) |
---|
452 | code = 405 |
---|
453 | title = 'Method Not Allowed' |
---|
454 | # override template since we need an environment variable |
---|
455 | template = ('The method %(REQUEST_METHOD)s is not allowed for ' |
---|
456 | 'this resource.\r\n%(detail)s') |
---|
457 | |
---|
458 | class HTTPNotAcceptable(HTTPClientError): |
---|
459 | code = 406 |
---|
460 | title = 'Not Acceptable' |
---|
461 | # override template since we need an environment variable |
---|
462 | template = ('The resource could not be generated that was ' |
---|
463 | 'acceptable to your browser (content\r\nof type ' |
---|
464 | '%(HTTP_ACCEPT)s).\r\n%(detail)s') |
---|
465 | |
---|
466 | class HTTPProxyAuthenticationRequired(HTTPClientError): |
---|
467 | code = 407 |
---|
468 | title = 'Proxy Authentication Required' |
---|
469 | explanation = ('Authentication /w a local proxy is needed.') |
---|
470 | |
---|
471 | class HTTPRequestTimeout(HTTPClientError): |
---|
472 | code = 408 |
---|
473 | title = 'Request Timeout' |
---|
474 | explanation = ('The server has waited too long for the request to ' |
---|
475 | 'be sent by the client.') |
---|
476 | |
---|
477 | class HTTPConflict(HTTPClientError): |
---|
478 | code = 409 |
---|
479 | title = 'Conflict' |
---|
480 | explanation = ('There was a conflict when trying to complete ' |
---|
481 | 'your request.') |
---|
482 | |
---|
483 | class HTTPGone(HTTPClientError): |
---|
484 | code = 410 |
---|
485 | title = 'Gone' |
---|
486 | explanation = ('This resource is no longer available. No forwarding ' |
---|
487 | 'address is given.') |
---|
488 | |
---|
489 | class HTTPLengthRequired(HTTPClientError): |
---|
490 | code = 411 |
---|
491 | title = 'Length Required' |
---|
492 | explanation = ('Content-Length header required.') |
---|
493 | |
---|
494 | class HTTPPreconditionFailed(HTTPClientError): |
---|
495 | code = 412 |
---|
496 | title = 'Precondition Failed' |
---|
497 | explanation = ('Request precondition failed.') |
---|
498 | |
---|
499 | class HTTPRequestEntityTooLarge(HTTPClientError): |
---|
500 | code = 413 |
---|
501 | title = 'Request Entity Too Large' |
---|
502 | explanation = ('The body of your request was too large for this server.') |
---|
503 | |
---|
504 | class HTTPRequestURITooLong(HTTPClientError): |
---|
505 | code = 414 |
---|
506 | title = 'Request-URI Too Long' |
---|
507 | explanation = ('The request URI was too long for this server.') |
---|
508 | |
---|
509 | class HTTPUnsupportedMediaType(HTTPClientError): |
---|
510 | code = 415 |
---|
511 | title = 'Unsupported Media Type' |
---|
512 | # override template since we need an environment variable |
---|
513 | template = ('The request media type %(CONTENT_TYPE)s is not ' |
---|
514 | 'supported by this server.\r\n%(detail)s') |
---|
515 | |
---|
516 | class HTTPRequestRangeNotSatisfiable(HTTPClientError): |
---|
517 | code = 416 |
---|
518 | title = 'Request Range Not Satisfiable' |
---|
519 | explanation = ('The Range requested is not available.') |
---|
520 | |
---|
521 | class HTTPExpectationFailed(HTTPClientError): |
---|
522 | code = 417 |
---|
523 | title = 'Expectation Failed' |
---|
524 | explanation = ('Expectation failed.') |
---|
525 | |
---|
526 | # |
---|
527 | # 5xx Server Error |
---|
528 | # |
---|
529 | # Response status codes beginning with the digit "5" indicate cases in |
---|
530 | # which the server is aware that it has erred or is incapable of |
---|
531 | # performing the request. Except when responding to a HEAD request, the |
---|
532 | # server SHOULD include an entity containing an explanation of the error |
---|
533 | # situation, and whether it is a temporary or permanent condition. User |
---|
534 | # agents SHOULD display any included entity to the user. These response |
---|
535 | # codes are applicable to any request method. |
---|
536 | # |
---|
537 | |
---|
538 | class HTTPServerError(HTTPError): |
---|
539 | """ |
---|
540 | base class for the 500's, where the server is in-error |
---|
541 | |
---|
542 | This is an error condition in which the server is presumed to be |
---|
543 | in-error. This is usually unexpected, and thus requires a traceback; |
---|
544 | ideally, opening a support ticket for the customer. Unless specialized, |
---|
545 | this is a '500 Internal Server Error' |
---|
546 | """ |
---|
547 | code = 500 |
---|
548 | title = 'Internal Server Error' |
---|
549 | explanation = ( |
---|
550 | 'The server has either erred or is incapable of performing\r\n' |
---|
551 | 'the requested operation.\r\n') |
---|
552 | |
---|
553 | class HTTPInternalServerError(HTTPServerError): |
---|
554 | pass |
---|
555 | |
---|
556 | class HTTPNotImplemented(HTTPServerError): |
---|
557 | code = 501 |
---|
558 | title = 'Not Implemented' |
---|
559 | # override template since we need an environment variable |
---|
560 | template = ('The request method %(REQUEST_METHOD)s is not implemented ' |
---|
561 | 'for this server.\r\n%(detail)s') |
---|
562 | |
---|
563 | class HTTPBadGateway(HTTPServerError): |
---|
564 | code = 502 |
---|
565 | title = 'Bad Gateway' |
---|
566 | explanation = ('Bad gateway.') |
---|
567 | |
---|
568 | class HTTPServiceUnavailable(HTTPServerError): |
---|
569 | code = 503 |
---|
570 | title = 'Service Unavailable' |
---|
571 | explanation = ('The server is currently unavailable. ' |
---|
572 | 'Please try again at a later time.') |
---|
573 | |
---|
574 | class HTTPGatewayTimeout(HTTPServerError): |
---|
575 | code = 504 |
---|
576 | title = 'Gateway Timeout' |
---|
577 | explanation = ('The gateway has timed out.') |
---|
578 | |
---|
579 | class HTTPVersionNotSupported(HTTPServerError): |
---|
580 | code = 505 |
---|
581 | title = 'HTTP Version Not Supported' |
---|
582 | explanation = ('The HTTP version is not supported.') |
---|
583 | |
---|
584 | # abstract HTTP related exceptions |
---|
585 | __all__ = ['HTTPException', 'HTTPRedirection', 'HTTPError' ] |
---|
586 | |
---|
587 | _exceptions = {} |
---|
588 | for name, value in globals().items(): |
---|
589 | if (isinstance(value, (type, types.ClassType)) and |
---|
590 | issubclass(value, HTTPException) and |
---|
591 | value.code): |
---|
592 | _exceptions[value.code] = value |
---|
593 | __all__.append(name) |
---|
594 | |
---|
595 | def get_exception(code): |
---|
596 | return _exceptions[code] |
---|
597 | |
---|
598 | ############################################################ |
---|
599 | ## Middleware implementation: |
---|
600 | ############################################################ |
---|
601 | |
---|
602 | class HTTPExceptionHandler(object): |
---|
603 | """ |
---|
604 | catches exceptions and turns them into proper HTTP responses |
---|
605 | |
---|
606 | Attributes: |
---|
607 | |
---|
608 | ``warning_level`` |
---|
609 | This attribute determines for what exceptions a stack |
---|
610 | trace is kept for lower level reporting; by default, it |
---|
611 | only keeps stack trace for 5xx, HTTPServerError exceptions. |
---|
612 | To keep a stack trace for 4xx, HTTPClientError exceptions, |
---|
613 | set this to 400. |
---|
614 | |
---|
615 | This middleware catches any exceptions (which are subclasses of |
---|
616 | ``HTTPException``) and turns them into proper HTTP responses. |
---|
617 | Note if the headers have already been sent, the stack trace is |
---|
618 | always maintained as this indicates a programming error. |
---|
619 | """ |
---|
620 | |
---|
621 | def __init__(self, application, warning_level=None): |
---|
622 | assert not warning_level or ( warning_level > 99 and |
---|
623 | warning_level < 600) |
---|
624 | self.warning_level = warning_level or 500 |
---|
625 | self.application = application |
---|
626 | |
---|
627 | def __call__(self, environ, start_response): |
---|
628 | environ['paste.httpexceptions'] = self |
---|
629 | environ.setdefault('paste.expected_exceptions', |
---|
630 | []).append(HTTPException) |
---|
631 | try: |
---|
632 | return self.application(environ, start_response) |
---|
633 | except HTTPException, exc: |
---|
634 | return exc(environ, start_response) |
---|
635 | |
---|
636 | def middleware(*args, **kw): |
---|
637 | import warnings |
---|
638 | # deprecated 13 dec 2005 |
---|
639 | warnings.warn('httpexceptions.middleware is deprecated; use ' |
---|
640 | 'make_middleware or HTTPExceptionHandler instead', |
---|
641 | DeprecationWarning, 2) |
---|
642 | return make_middleware(*args, **kw) |
---|
643 | |
---|
644 | def make_middleware(app, global_conf=None, warning_level=None): |
---|
645 | """ |
---|
646 | ``httpexceptions`` middleware; this catches any |
---|
647 | ``paste.httpexceptions.HTTPException`` exceptions (exceptions like |
---|
648 | ``HTTPNotFound``, ``HTTPMovedPermanently``, etc) and turns them |
---|
649 | into proper HTTP responses. |
---|
650 | |
---|
651 | ``warning_level`` can be an integer corresponding to an HTTP code. |
---|
652 | Any code over that value will be passed 'up' the chain, potentially |
---|
653 | reported on by another piece of middleware. |
---|
654 | """ |
---|
655 | if warning_level: |
---|
656 | warning_level = int(warning_level) |
---|
657 | return HTTPExceptionHandler(app, warning_level=warning_level) |
---|
658 | |
---|
659 | __all__.extend(['HTTPExceptionHandler', 'get_exception']) |
---|
660 | |
---|