668 lines
24 KiB
Python
668 lines
24 KiB
Python
|
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
|
||
|
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
|
||
|
# (c) 2005 Ian Bicking, Clark C. Evans and contributors
|
||
|
# This module is part of the Python Paste Project and is released under
|
||
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||
|
# Some of this code was funded by http://prometheusresearch.com
|
||
|
"""
|
||
|
HTTP Exception Middleware
|
||
|
|
||
|
This module processes Python exceptions that relate to HTTP exceptions
|
||
|
by defining a set of exceptions, all subclasses of HTTPException, and a
|
||
|
request handler (`middleware`) that catches these exceptions and turns
|
||
|
them into proper responses.
|
||
|
|
||
|
This module defines exceptions according to RFC 2068 [1]_ : codes with
|
||
|
100-300 are not really errors; 400's are client errors, and 500's are
|
||
|
server errors. According to the WSGI specification [2]_ , the application
|
||
|
can call ``start_response`` more then once only under two conditions:
|
||
|
(a) the response has not yet been sent, or (b) if the second and
|
||
|
subsequent invocations of ``start_response`` have a valid ``exc_info``
|
||
|
argument obtained from ``sys.exc_info()``. The WSGI specification then
|
||
|
requires the server or gateway to handle the case where content has been
|
||
|
sent and then an exception was encountered.
|
||
|
|
||
|
Exceptions in the 5xx range and those raised after ``start_response``
|
||
|
has been called are treated as serious errors and the ``exc_info`` is
|
||
|
filled-in with information needed for a lower level module to generate a
|
||
|
stack trace and log information.
|
||
|
|
||
|
Exception
|
||
|
HTTPException
|
||
|
HTTPRedirection
|
||
|
* 300 - HTTPMultipleChoices
|
||
|
* 301 - HTTPMovedPermanently
|
||
|
* 302 - HTTPFound
|
||
|
* 303 - HTTPSeeOther
|
||
|
* 304 - HTTPNotModified
|
||
|
* 305 - HTTPUseProxy
|
||
|
* 306 - Unused (not implemented, obviously)
|
||
|
* 307 - HTTPTemporaryRedirect
|
||
|
HTTPError
|
||
|
HTTPClientError
|
||
|
* 400 - HTTPBadRequest
|
||
|
* 401 - HTTPUnauthorized
|
||
|
* 402 - HTTPPaymentRequired
|
||
|
* 403 - HTTPForbidden
|
||
|
* 404 - HTTPNotFound
|
||
|
* 405 - HTTPMethodNotAllowed
|
||
|
* 406 - HTTPNotAcceptable
|
||
|
* 407 - HTTPProxyAuthenticationRequired
|
||
|
* 408 - HTTPRequestTimeout
|
||
|
* 409 - HTTPConfict
|
||
|
* 410 - HTTPGone
|
||
|
* 411 - HTTPLengthRequired
|
||
|
* 412 - HTTPPreconditionFailed
|
||
|
* 413 - HTTPRequestEntityTooLarge
|
||
|
* 414 - HTTPRequestURITooLong
|
||
|
* 415 - HTTPUnsupportedMediaType
|
||
|
* 416 - HTTPRequestRangeNotSatisfiable
|
||
|
* 417 - HTTPExpectationFailed
|
||
|
* 429 - HTTPTooManyRequests
|
||
|
HTTPServerError
|
||
|
* 500 - HTTPInternalServerError
|
||
|
* 501 - HTTPNotImplemented
|
||
|
* 502 - HTTPBadGateway
|
||
|
* 503 - HTTPServiceUnavailable
|
||
|
* 504 - HTTPGatewayTimeout
|
||
|
* 505 - HTTPVersionNotSupported
|
||
|
|
||
|
References:
|
||
|
|
||
|
.. [1] http://www.python.org/peps/pep-0333.html#error-handling
|
||
|
.. [2] http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5
|
||
|
|
||
|
"""
|
||
|
|
||
|
import six
|
||
|
from paste.wsgilib import catch_errors_app
|
||
|
from paste.response import has_header, header_value, replace_header
|
||
|
from paste.request import resolve_relative_url
|
||
|
from paste.util.quoting import strip_html, html_quote, no_quote, comment_quote
|
||
|
|
||
|
SERVER_NAME = 'WSGI Server'
|
||
|
TEMPLATE = """\
|
||
|
<html>\r
|
||
|
<head><title>%(title)s</title></head>\r
|
||
|
<body>\r
|
||
|
<h1>%(title)s</h1>\r
|
||
|
<p>%(body)s</p>\r
|
||
|
<hr noshade>\r
|
||
|
<div align="right">%(server)s</div>\r
|
||
|
</body>\r
|
||
|
</html>\r
|
||
|
"""
|
||
|
|
||
|
class HTTPException(Exception):
|
||
|
"""
|
||
|
the HTTP exception base class
|
||
|
|
||
|
This encapsulates an HTTP response that interrupts normal application
|
||
|
flow; but one which is not necessarly an error condition. For
|
||
|
example, codes in the 300's are exceptions in that they interrupt
|
||
|
normal processing; however, they are not considered errors.
|
||
|
|
||
|
This class is complicated by 4 factors:
|
||
|
|
||
|
1. The content given to the exception may either be plain-text or
|
||
|
as html-text.
|
||
|
|
||
|
2. The template may want to have string-substitutions taken from
|
||
|
the current ``environ`` or values from incoming headers. This
|
||
|
is especially troublesome due to case sensitivity.
|
||
|
|
||
|
3. The final output may either be text/plain or text/html
|
||
|
mime-type as requested by the client application.
|
||
|
|
||
|
4. Each exception has a default explanation, but those who
|
||
|
raise exceptions may want to provide additional detail.
|
||
|
|
||
|
Attributes:
|
||
|
|
||
|
``code``
|
||
|
the HTTP status code for the exception
|
||
|
|
||
|
``title``
|
||
|
remainder of the status line (stuff after the code)
|
||
|
|
||
|
``explanation``
|
||
|
a plain-text explanation of the error message that is
|
||
|
not subject to environment or header substitutions;
|
||
|
it is accessible in the template via %(explanation)s
|
||
|
|
||
|
``detail``
|
||
|
a plain-text message customization that is not subject
|
||
|
to environment or header substitutions; accessible in
|
||
|
the template via %(detail)s
|
||
|
|
||
|
``template``
|
||
|
a content fragment (in HTML) used for environment and
|
||
|
header substitution; the default template includes both
|
||
|
the explanation and further detail provided in the
|
||
|
message
|
||
|
|
||
|
``required_headers``
|
||
|
a sequence of headers which are required for proper
|
||
|
construction of the exception
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
``detail``
|
||
|
a plain-text override of the default ``detail``
|
||
|
|
||
|
``headers``
|
||
|
a list of (k,v) header pairs
|
||
|
|
||
|
``comment``
|
||
|
a plain-text additional information which is
|
||
|
usually stripped/hidden for end-users
|
||
|
|
||
|
To override the template (which is HTML content) or the plain-text
|
||
|
explanation, one must subclass the given exception; or customize it
|
||
|
after it has been created. This particular breakdown of a message
|
||
|
into explanation, detail and template allows both the creation of
|
||
|
plain-text and html messages for various clients as well as
|
||
|
error-free substitution of environment variables and headers.
|
||
|
"""
|
||
|
|
||
|
code = None
|
||
|
title = None
|
||
|
explanation = ''
|
||
|
detail = ''
|
||
|
comment = ''
|
||
|
template = "%(explanation)s\r\n<br/>%(detail)s\r\n<!-- %(comment)s -->"
|
||
|
required_headers = ()
|
||
|
|
||
|
def __init__(self, detail=None, headers=None, comment=None):
|
||
|
assert self.code, "Do not directly instantiate abstract exceptions."
|
||
|
assert isinstance(headers, (type(None), list)), (
|
||
|
"headers must be None or a list: %r"
|
||
|
% headers)
|
||
|
assert isinstance(detail, (type(None), six.binary_type, six.text_type)), (
|
||
|
"detail must be None or a string: %r" % detail)
|
||
|
assert isinstance(comment, (type(None), six.binary_type, six.text_type)), (
|
||
|
"comment must be None or a string: %r" % comment)
|
||
|
self.headers = headers or tuple()
|
||
|
for req in self.required_headers:
|
||
|
assert headers and has_header(headers, req), (
|
||
|
"Exception %s must be passed the header %r "
|
||
|
"(got headers: %r)"
|
||
|
% (self.__class__.__name__, req, headers))
|
||
|
if detail is not None:
|
||
|
self.detail = detail
|
||
|
if comment is not None:
|
||
|
self.comment = comment
|
||
|
Exception.__init__(self,"%s %s\n%s\n%s\n" % (
|
||
|
self.code, self.title, self.explanation, self.detail))
|
||
|
|
||
|
def make_body(self, environ, template, escfunc, comment_escfunc=None):
|
||
|
comment_escfunc = comment_escfunc or escfunc
|
||
|
args = {'explanation': escfunc(self.explanation),
|
||
|
'detail': escfunc(self.detail),
|
||
|
'comment': comment_escfunc(self.comment)}
|
||
|
if HTTPException.template != self.template:
|
||
|
for (k, v) in environ.items():
|
||
|
args[k] = escfunc(v)
|
||
|
if self.headers:
|
||
|
for (k, v) in self.headers:
|
||
|
args[k.lower()] = escfunc(v)
|
||
|
if six.PY2:
|
||
|
for key, value in args.items():
|
||
|
if isinstance(value, six.text_type):
|
||
|
args[key] = value.encode('utf8', 'xmlcharrefreplace')
|
||
|
return template % args
|
||
|
|
||
|
def plain(self, environ):
|
||
|
""" text/plain representation of the exception """
|
||
|
body = self.make_body(environ, strip_html(self.template), no_quote, comment_quote)
|
||
|
return ('%s %s\r\n%s\r\n' % (self.code, self.title, body))
|
||
|
|
||
|
def html(self, environ):
|
||
|
""" text/html representation of the exception """
|
||
|
body = self.make_body(environ, self.template, html_quote, comment_quote)
|
||
|
return TEMPLATE % {
|
||
|
'title': self.title,
|
||
|
'code': self.code,
|
||
|
'server': SERVER_NAME,
|
||
|
'body': body }
|
||
|
|
||
|
def prepare_content(self, environ):
|
||
|
if self.headers:
|
||
|
headers = list(self.headers)
|
||
|
else:
|
||
|
headers = []
|
||
|
if 'html' in environ.get('HTTP_ACCEPT','') or \
|
||
|
'*/*' in environ.get('HTTP_ACCEPT',''):
|
||
|
replace_header(headers, 'content-type', 'text/html')
|
||
|
content = self.html(environ)
|
||
|
else:
|
||
|
replace_header(headers, 'content-type', 'text/plain')
|
||
|
content = self.plain(environ)
|
||
|
if isinstance(content, six.text_type):
|
||
|
content = content.encode('utf8')
|
||
|
cur_content_type = (
|
||
|
header_value(headers, 'content-type')
|
||
|
or 'text/html')
|
||
|
replace_header(
|
||
|
headers, 'content-type',
|
||
|
cur_content_type + '; charset=utf8')
|
||
|
return headers, content
|
||
|
|
||
|
def response(self, environ):
|
||
|
from paste.wsgiwrappers import WSGIResponse
|
||
|
headers, content = self.prepare_content(environ)
|
||
|
resp = WSGIResponse(code=self.code, content=content)
|
||
|
resp.headers = resp.headers.fromlist(headers)
|
||
|
return resp
|
||
|
|
||
|
def wsgi_application(self, environ, start_response, exc_info=None):
|
||
|
"""
|
||
|
This exception as a WSGI application
|
||
|
"""
|
||
|
headers, content = self.prepare_content(environ)
|
||
|
start_response('%s %s' % (self.code, self.title),
|
||
|
headers,
|
||
|
exc_info)
|
||
|
return [content]
|
||
|
|
||
|
__call__ = wsgi_application
|
||
|
|
||
|
def __repr__(self):
|
||
|
return '<%s %s; code=%s>' % (self.__class__.__name__,
|
||
|
self.title, self.code)
|
||
|
|
||
|
class HTTPError(HTTPException):
|
||
|
"""
|
||
|
base class for status codes in the 400's and 500's
|
||
|
|
||
|
This is an exception which indicates that an error has occurred,
|
||
|
and that any work in progress should not be committed. These are
|
||
|
typically results in the 400's and 500's.
|
||
|
"""
|
||
|
|
||
|
#
|
||
|
# 3xx Redirection
|
||
|
#
|
||
|
# This class of status code indicates that further action needs to be
|
||
|
# taken by the user agent in order to fulfill the request. The action
|
||
|
# required MAY be carried out by the user agent without interaction with
|
||
|
# the user if and only if the method used in the second request is GET or
|
||
|
# HEAD. A client SHOULD detect infinite redirection loops, since such
|
||
|
# loops generate network traffic for each redirection.
|
||
|
#
|
||
|
|
||
|
class HTTPRedirection(HTTPException):
|
||
|
"""
|
||
|
base class for 300's status code (redirections)
|
||
|
|
||
|
This is an abstract base class for 3xx redirection. It indicates
|
||
|
that further action needs to be taken by the user agent in order
|
||
|
to fulfill the request. It does not necessarly signal an error
|
||
|
condition.
|
||
|
"""
|
||
|
|
||
|
class _HTTPMove(HTTPRedirection):
|
||
|
"""
|
||
|
redirections which require a Location field
|
||
|
|
||
|
Since a 'Location' header is a required attribute of 301, 302, 303,
|
||
|
305 and 307 (but not 304), this base class provides the mechanics to
|
||
|
make this easy. While this has the same parameters as HTTPException,
|
||
|
if a location is not provided in the headers; it is assumed that the
|
||
|
detail _is_ the location (this for backward compatibility, otherwise
|
||
|
we'd add a new attribute).
|
||
|
"""
|
||
|
required_headers = ('location',)
|
||
|
explanation = 'The resource has been moved to'
|
||
|
template = (
|
||
|
'%(explanation)s <a href="%(location)s">%(location)s</a>;\r\n'
|
||
|
'you should be redirected automatically.\r\n'
|
||
|
'%(detail)s\r\n<!-- %(comment)s -->')
|
||
|
|
||
|
def __init__(self, detail=None, headers=None, comment=None):
|
||
|
assert isinstance(headers, (type(None), list))
|
||
|
headers = headers or []
|
||
|
location = header_value(headers,'location')
|
||
|
if not location:
|
||
|
location = detail
|
||
|
detail = ''
|
||
|
headers.append(('location', location))
|
||
|
assert location, ("HTTPRedirection specified neither a "
|
||
|
"location in the headers nor did it "
|
||
|
"provide a detail argument.")
|
||
|
HTTPRedirection.__init__(self, location, headers, comment)
|
||
|
if detail is not None:
|
||
|
self.detail = detail
|
||
|
|
||
|
def relative_redirect(cls, dest_uri, environ, detail=None, headers=None, comment=None):
|
||
|
"""
|
||
|
Create a redirect object with the dest_uri, which may be relative,
|
||
|
considering it relative to the uri implied by the given environ.
|
||
|
"""
|
||
|
location = resolve_relative_url(dest_uri, environ)
|
||
|
headers = headers or []
|
||
|
headers.append(('Location', location))
|
||
|
return cls(detail=detail, headers=headers, comment=comment)
|
||
|
|
||
|
relative_redirect = classmethod(relative_redirect)
|
||
|
|
||
|
def location(self):
|
||
|
for name, value in self.headers:
|
||
|
if name.lower() == 'location':
|
||
|
return value
|
||
|
else:
|
||
|
raise KeyError("No location set for %s" % self)
|
||
|
|
||
|
class HTTPMultipleChoices(_HTTPMove):
|
||
|
code = 300
|
||
|
title = 'Multiple Choices'
|
||
|
|
||
|
class HTTPMovedPermanently(_HTTPMove):
|
||
|
code = 301
|
||
|
title = 'Moved Permanently'
|
||
|
|
||
|
class HTTPFound(_HTTPMove):
|
||
|
code = 302
|
||
|
title = 'Found'
|
||
|
explanation = 'The resource was found at'
|
||
|
|
||
|
# This one is safe after a POST (the redirected location will be
|
||
|
# retrieved with GET):
|
||
|
class HTTPSeeOther(_HTTPMove):
|
||
|
code = 303
|
||
|
title = 'See Other'
|
||
|
|
||
|
class HTTPNotModified(HTTPRedirection):
|
||
|
# @@: but not always (HTTP section 14.18.1)...?
|
||
|
# @@: Removed 'date' requirement, as its not required for an ETag
|
||
|
# @@: FIXME: This should require either an ETag or a date header
|
||
|
code = 304
|
||
|
title = 'Not Modified'
|
||
|
message = ''
|
||
|
# @@: should include date header, optionally other headers
|
||
|
# @@: should not return a content body
|
||
|
def plain(self, environ):
|
||
|
return ''
|
||
|
def html(self, environ):
|
||
|
""" text/html representation of the exception """
|
||
|
return ''
|
||
|
|
||
|
class HTTPUseProxy(_HTTPMove):
|
||
|
# @@: OK, not a move, but looks a little like one
|
||
|
code = 305
|
||
|
title = 'Use Proxy'
|
||
|
explanation = (
|
||
|
'The resource must be accessed through a proxy '
|
||
|
'located at')
|
||
|
|
||
|
class HTTPTemporaryRedirect(_HTTPMove):
|
||
|
code = 307
|
||
|
title = 'Temporary Redirect'
|
||
|
|
||
|
#
|
||
|
# 4xx Client Error
|
||
|
#
|
||
|
# The 4xx class of status code is intended for cases in which the client
|
||
|
# seems to have erred. Except when responding to a HEAD request, the
|
||
|
# server SHOULD include an entity containing an explanation of the error
|
||
|
# situation, and whether it is a temporary or permanent condition. These
|
||
|
# status codes are applicable to any request method. User agents SHOULD
|
||
|
# display any included entity to the user.
|
||
|
#
|
||
|
|
||
|
class HTTPClientError(HTTPError):
|
||
|
"""
|
||
|
base class for the 400's, where the client is in-error
|
||
|
|
||
|
This is an error condition in which the client is presumed to be
|
||
|
in-error. This is an expected problem, and thus is not considered
|
||
|
a bug. A server-side traceback is not warranted. Unless specialized,
|
||
|
this is a '400 Bad Request'
|
||
|
"""
|
||
|
code = 400
|
||
|
title = 'Bad Request'
|
||
|
explanation = ('The server could not comply with the request since\r\n'
|
||
|
'it is either malformed or otherwise incorrect.\r\n')
|
||
|
|
||
|
class HTTPBadRequest(HTTPClientError):
|
||
|
pass
|
||
|
|
||
|
class HTTPUnauthorized(HTTPClientError):
|
||
|
code = 401
|
||
|
title = 'Unauthorized'
|
||
|
explanation = (
|
||
|
'This server could not verify that you are authorized to\r\n'
|
||
|
'access the document you requested. Either you supplied the\r\n'
|
||
|
'wrong credentials (e.g., bad password), or your browser\r\n'
|
||
|
'does not understand how to supply the credentials required.\r\n')
|
||
|
|
||
|
class HTTPPaymentRequired(HTTPClientError):
|
||
|
code = 402
|
||
|
title = 'Payment Required'
|
||
|
explanation = ('Access was denied for financial reasons.')
|
||
|
|
||
|
class HTTPForbidden(HTTPClientError):
|
||
|
code = 403
|
||
|
title = 'Forbidden'
|
||
|
explanation = ('Access was denied to this resource.')
|
||
|
|
||
|
class HTTPNotFound(HTTPClientError):
|
||
|
code = 404
|
||
|
title = 'Not Found'
|
||
|
explanation = ('The resource could not be found.')
|
||
|
|
||
|
class HTTPMethodNotAllowed(HTTPClientError):
|
||
|
required_headers = ('allow',)
|
||
|
code = 405
|
||
|
title = 'Method Not Allowed'
|
||
|
# override template since we need an environment variable
|
||
|
template = ('The method %(REQUEST_METHOD)s is not allowed for '
|
||
|
'this resource.\r\n%(detail)s')
|
||
|
|
||
|
class HTTPNotAcceptable(HTTPClientError):
|
||
|
code = 406
|
||
|
title = 'Not Acceptable'
|
||
|
# override template since we need an environment variable
|
||
|
template = ('The resource could not be generated that was '
|
||
|
'acceptable to your browser (content\r\nof type '
|
||
|
'%(HTTP_ACCEPT)s).\r\n%(detail)s')
|
||
|
|
||
|
class HTTPProxyAuthenticationRequired(HTTPClientError):
|
||
|
code = 407
|
||
|
title = 'Proxy Authentication Required'
|
||
|
explanation = ('Authentication /w a local proxy is needed.')
|
||
|
|
||
|
class HTTPRequestTimeout(HTTPClientError):
|
||
|
code = 408
|
||
|
title = 'Request Timeout'
|
||
|
explanation = ('The server has waited too long for the request to '
|
||
|
'be sent by the client.')
|
||
|
|
||
|
class HTTPConflict(HTTPClientError):
|
||
|
code = 409
|
||
|
title = 'Conflict'
|
||
|
explanation = ('There was a conflict when trying to complete '
|
||
|
'your request.')
|
||
|
|
||
|
class HTTPGone(HTTPClientError):
|
||
|
code = 410
|
||
|
title = 'Gone'
|
||
|
explanation = ('This resource is no longer available. No forwarding '
|
||
|
'address is given.')
|
||
|
|
||
|
class HTTPLengthRequired(HTTPClientError):
|
||
|
code = 411
|
||
|
title = 'Length Required'
|
||
|
explanation = ('Content-Length header required.')
|
||
|
|
||
|
class HTTPPreconditionFailed(HTTPClientError):
|
||
|
code = 412
|
||
|
title = 'Precondition Failed'
|
||
|
explanation = ('Request precondition failed.')
|
||
|
|
||
|
class HTTPRequestEntityTooLarge(HTTPClientError):
|
||
|
code = 413
|
||
|
title = 'Request Entity Too Large'
|
||
|
explanation = ('The body of your request was too large for this server.')
|
||
|
|
||
|
class HTTPRequestURITooLong(HTTPClientError):
|
||
|
code = 414
|
||
|
title = 'Request-URI Too Long'
|
||
|
explanation = ('The request URI was too long for this server.')
|
||
|
|
||
|
class HTTPUnsupportedMediaType(HTTPClientError):
|
||
|
code = 415
|
||
|
title = 'Unsupported Media Type'
|
||
|
# override template since we need an environment variable
|
||
|
template = ('The request media type %(CONTENT_TYPE)s is not '
|
||
|
'supported by this server.\r\n%(detail)s')
|
||
|
|
||
|
class HTTPRequestRangeNotSatisfiable(HTTPClientError):
|
||
|
code = 416
|
||
|
title = 'Request Range Not Satisfiable'
|
||
|
explanation = ('The Range requested is not available.')
|
||
|
|
||
|
class HTTPExpectationFailed(HTTPClientError):
|
||
|
code = 417
|
||
|
title = 'Expectation Failed'
|
||
|
explanation = ('Expectation failed.')
|
||
|
|
||
|
class HTTPTooManyRequests(HTTPClientError):
|
||
|
code = 429
|
||
|
title = 'Too Many Requests'
|
||
|
explanation = ('The client has sent too many requests to the server.')
|
||
|
|
||
|
#
|
||
|
# 5xx Server Error
|
||
|
#
|
||
|
# Response status codes beginning with the digit "5" indicate cases in
|
||
|
# which the server is aware that it has erred or is incapable of
|
||
|
# performing the request. Except when responding to a HEAD request, the
|
||
|
# server SHOULD include an entity containing an explanation of the error
|
||
|
# situation, and whether it is a temporary or permanent condition. User
|
||
|
# agents SHOULD display any included entity to the user. These response
|
||
|
# codes are applicable to any request method.
|
||
|
#
|
||
|
|
||
|
class HTTPServerError(HTTPError):
|
||
|
"""
|
||
|
base class for the 500's, where the server is in-error
|
||
|
|
||
|
This is an error condition in which the server is presumed to be
|
||
|
in-error. This is usually unexpected, and thus requires a traceback;
|
||
|
ideally, opening a support ticket for the customer. Unless specialized,
|
||
|
this is a '500 Internal Server Error'
|
||
|
"""
|
||
|
code = 500
|
||
|
title = 'Internal Server Error'
|
||
|
explanation = (
|
||
|
'The server has either erred or is incapable of performing\r\n'
|
||
|
'the requested operation.\r\n')
|
||
|
|
||
|
class HTTPInternalServerError(HTTPServerError):
|
||
|
pass
|
||
|
|
||
|
class HTTPNotImplemented(HTTPServerError):
|
||
|
code = 501
|
||
|
title = 'Not Implemented'
|
||
|
# override template since we need an environment variable
|
||
|
template = ('The request method %(REQUEST_METHOD)s is not implemented '
|
||
|
'for this server.\r\n%(detail)s')
|
||
|
|
||
|
class HTTPBadGateway(HTTPServerError):
|
||
|
code = 502
|
||
|
title = 'Bad Gateway'
|
||
|
explanation = ('Bad gateway.')
|
||
|
|
||
|
class HTTPServiceUnavailable(HTTPServerError):
|
||
|
code = 503
|
||
|
title = 'Service Unavailable'
|
||
|
explanation = ('The server is currently unavailable. '
|
||
|
'Please try again at a later time.')
|
||
|
|
||
|
class HTTPGatewayTimeout(HTTPServerError):
|
||
|
code = 504
|
||
|
title = 'Gateway Timeout'
|
||
|
explanation = ('The gateway has timed out.')
|
||
|
|
||
|
class HTTPVersionNotSupported(HTTPServerError):
|
||
|
code = 505
|
||
|
title = 'HTTP Version Not Supported'
|
||
|
explanation = ('The HTTP version is not supported.')
|
||
|
|
||
|
# abstract HTTP related exceptions
|
||
|
__all__ = ['HTTPException', 'HTTPRedirection', 'HTTPError' ]
|
||
|
|
||
|
_exceptions = {}
|
||
|
for name, value in six.iteritems(dict(globals())):
|
||
|
if (isinstance(value, (type, six.class_types)) and
|
||
|
issubclass(value, HTTPException) and
|
||
|
value.code):
|
||
|
_exceptions[value.code] = value
|
||
|
__all__.append(name)
|
||
|
|
||
|
def get_exception(code):
|
||
|
return _exceptions[code]
|
||
|
|
||
|
############################################################
|
||
|
## Middleware implementation:
|
||
|
############################################################
|
||
|
|
||
|
class HTTPExceptionHandler(object):
|
||
|
"""
|
||
|
catches exceptions and turns them into proper HTTP responses
|
||
|
|
||
|
This middleware catches any exceptions (which are subclasses of
|
||
|
``HTTPException``) and turns them into proper HTTP responses.
|
||
|
Note if the headers have already been sent, the stack trace is
|
||
|
always maintained as this indicates a programming error.
|
||
|
|
||
|
Note that you must raise the exception before returning the
|
||
|
app_iter, and you cannot use this with generator apps that don't
|
||
|
raise an exception until after their app_iter is iterated over.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, application, warning_level=None):
|
||
|
assert not warning_level or ( warning_level > 99 and
|
||
|
warning_level < 600)
|
||
|
if warning_level is not None:
|
||
|
import warnings
|
||
|
warnings.warn('The warning_level parameter is not used or supported',
|
||
|
DeprecationWarning, 2)
|
||
|
self.warning_level = warning_level or 500
|
||
|
self.application = application
|
||
|
|
||
|
def __call__(self, environ, start_response):
|
||
|
environ['paste.httpexceptions'] = self
|
||
|
environ.setdefault('paste.expected_exceptions',
|
||
|
[]).append(HTTPException)
|
||
|
try:
|
||
|
return self.application(environ, start_response)
|
||
|
except HTTPException as exc:
|
||
|
return exc(environ, start_response)
|
||
|
|
||
|
def middleware(*args, **kw):
|
||
|
import warnings
|
||
|
# deprecated 13 dec 2005
|
||
|
warnings.warn('httpexceptions.middleware is deprecated; use '
|
||
|
'make_middleware or HTTPExceptionHandler instead',
|
||
|
DeprecationWarning, 2)
|
||
|
return make_middleware(*args, **kw)
|
||
|
|
||
|
def make_middleware(app, global_conf=None, warning_level=None):
|
||
|
"""
|
||
|
``httpexceptions`` middleware; this catches any
|
||
|
``paste.httpexceptions.HTTPException`` exceptions (exceptions like
|
||
|
``HTTPNotFound``, ``HTTPMovedPermanently``, etc) and turns them
|
||
|
into proper HTTP responses.
|
||
|
|
||
|
``warning_level`` can be an integer corresponding to an HTTP code.
|
||
|
Any code over that value will be passed 'up' the chain, potentially
|
||
|
reported on by another piece of middleware.
|
||
|
"""
|
||
|
if warning_level:
|
||
|
warning_level = int(warning_level)
|
||
|
return HTTPExceptionHandler(app, warning_level=warning_level)
|
||
|
|
||
|
__all__.extend(['HTTPExceptionHandler', 'get_exception'])
|