# (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 """ WSGI middleware Processes Python exceptions that relate to HTTP exceptions. This defines a set of extensions, all subclasses of HTTPException, and a middleware (`middleware`) that catches these exceptions and turns them into proper responses. """ import types class HTTPException(Exception): code = None title = None message = None # @@: not currently used: required_headers = () def __init__(self, message=None, headers=None): self.headers = headers if message is not None: self.message = message Exception.__init__(self, self.message) def html(self, environ): message = self.message args = environ.copy() if self.headers: environ.update(self.headers) message = message % args return ('%(title)s\n' '\n' '

%(title)s

\n' '

%(message)s

\n' '
\n' '
WSGI server
\n' '\n' % {'title': self.title, 'code': self.code, 'message': message}) def __repr__(self): return '<%s %s; code=%s>' % (self.__class__.__name__, self.title, self.code) class _HTTPMove(HTTPException): required_headers = ('location',) message = ('The resource has been moved to ' '%(location)s; you should be redirected automatically.') class HTTPMovedPermanently(_HTTPMove): code = 301 title = 'Moved Permanently' class HTTPFound(_HTTPMove): code = 302 title = 'Found' # 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(HTTPException): # @@: but not always (HTTP section 14.18.1)...? required_headers = ('date',) code = 304 title = 'Not Modified' message = '' # @@: should include date header, optionally other headers class HTTPUserProxy(_HTTPMove): # @@: OK, not a move, but looks a little like one code = 305 title = 'Use Proxy' message = ('This resource must be accessed through the proxy located ' 'at %(location)s') class HTTPTemporaryRedirect(_HTTPMove): code = 307 title = 'Temporary Redirect' class HTTPBadRequest(HTTPException): code = 400 title = 'Bad Request' message = ('The server could not understand your request') class HTTPUnauthorized(HTTPException): required_headers = ('WWW-Authenticate',) code = 401 title = 'Unauthorized' # @@: should require WWW-Authenticate header message = ('Authorization is required to access this resource; ' 'you must login.') class HTTPForbidden(HTTPException): code = 403 title = 'Forbidden' message = ('Access was denied to this resource.') class HTTPNotFound(HTTPException): code = 404 title = 'Not Found' message = ('The resource could not be found.') class HTTPMethodNotAllowed(HTTPException): required_headers = ('allowed',) code = 405 title = 'Method Not Allowed' message = ('The method %(REQUEST_METHOD)s is not allowed for this ' 'resource.') class HTTPNotAcceptable(HTTPException): code = 406 title = 'Not Acceptable' message = ('The resource could not be generated that was acceptable ' 'to your browser (content of type %(HTTP_ACCEPT)s).') class HTTPConfict(HTTPException): code = 409 title = 'Conflict' message = ('There was a conflict when trying to complete your ' 'request.') class HTTPGone(HTTPException): code = 410 title = 'Gone' message = ('This resource is no longer available. No forwarding ' 'address is aavailable.') class HTTPLengthRequired(HTTPException): code = 411 title = 'Length Required' message = ('Content-Length header required.') class HTTPPreconditionFailed(HTTPException): code = 412 title = 'Precondition Failed' message = ('Request precondition failed.') class HTTPRequestEntityTooLarge(HTTPException): code = 413 title = 'Request Entity Too Large' message = ('The body of your request was too large for this server.') class HTTPRequestURITooLong(HTTPException): code = 414 title = 'Request-URI Too Long' message = ('The request URI was too long for this server.') class HTTPUnsupportedMediaType(HTTPException): code = 415 title = 'Unsupported Media Type' message = ('The request media type %(CONTENT_TYPE)s is not ' 'supported by this server.') class HTTPRequestRangeNotSatisfiable(HTTPException): code = 416 title = 'Request Range Not Satisfiable' message = ('The Range requested is not available.') class HTTPExpectationFailed(HTTPException): code = 417 title = 'Expectation Failed' message = ('Expectation failed.') class HTTPServerError(HTTPException): code = 500 title = 'Internal Server Error' message = ('An internal server error occurred.') class HTTPNotImplemented(HTTPException): coded = 501 title = 'Not Implemented' message = ('The request method %(REQUEST_METHOD)s is not implemented ' 'for this server.') class HTTPBadGateway(HTTPException): code = 502 title = 'Bad Gateway' message = ('Bad gateway.') class HTTPServiceUnavailable(HTTPException): code = 503 title = 'Service Unavailable' message = ('The server is currently unavailable. Please try again ' 'at a later time.') class HTTPGatewayTimeout(HTTPException): code = 504 title = 'Gateway Timeout' message = ('The gateway has timed out.') class HTTPHttpVersionNotSupported(HTTPException): code = 505 title = 'HTTP Version Not Supported' message = ('The HTTP version is not supported.') __all__ = [] _exceptions = {} for name, value in globals().items(): if (isinstance(value, (type, types.ClassType)) and issubclass(value, HTTPException) and value.code): _exceptions[value.code] = value __all__.append(name) def get_exception(code): return _exceptions[code] ############################################################ ## Middleware implementation: ############################################################ def middleware(application, global_conf=None): """ This middleware catches any exceptions (which are subclasses of `HTTPException`) and turns them into proper HTTP responses. """ def start_application(environ, start_response): app_started = [] def checked_start_response(status, headers, exc_info=None): app_started.append(None) return start_response(status, headers, exc_info) try: return application(environ, checked_start_response) except HTTPException, e: if app_started: # They've already started the response, so we can't # do the right thing anymore. raise headers = {'content-type': 'text/html'} if e.headers: headers.update(e.headers) start_response('%s %s' % (e.code, e.title), headers.items()) return [e.html(environ)] return start_application __all__.extend(['middleware', 'get_exception'])