453 lines
10 KiB
Python
453 lines
10 KiB
Python
import warnings
|
|
from typing import Any, Dict, Iterable, List, Optional, Set # noqa
|
|
|
|
from yarl import URL
|
|
|
|
from .typedefs import LooseHeaders, StrOrURL
|
|
from .web_response import Response
|
|
|
|
__all__ = (
|
|
"HTTPException",
|
|
"HTTPError",
|
|
"HTTPRedirection",
|
|
"HTTPSuccessful",
|
|
"HTTPOk",
|
|
"HTTPCreated",
|
|
"HTTPAccepted",
|
|
"HTTPNonAuthoritativeInformation",
|
|
"HTTPNoContent",
|
|
"HTTPResetContent",
|
|
"HTTPPartialContent",
|
|
"HTTPMove",
|
|
"HTTPMultipleChoices",
|
|
"HTTPMovedPermanently",
|
|
"HTTPFound",
|
|
"HTTPSeeOther",
|
|
"HTTPNotModified",
|
|
"HTTPUseProxy",
|
|
"HTTPTemporaryRedirect",
|
|
"HTTPPermanentRedirect",
|
|
"HTTPClientError",
|
|
"HTTPBadRequest",
|
|
"HTTPUnauthorized",
|
|
"HTTPPaymentRequired",
|
|
"HTTPForbidden",
|
|
"HTTPNotFound",
|
|
"HTTPMethodNotAllowed",
|
|
"HTTPNotAcceptable",
|
|
"HTTPProxyAuthenticationRequired",
|
|
"HTTPRequestTimeout",
|
|
"HTTPConflict",
|
|
"HTTPGone",
|
|
"HTTPLengthRequired",
|
|
"HTTPPreconditionFailed",
|
|
"HTTPRequestEntityTooLarge",
|
|
"HTTPRequestURITooLong",
|
|
"HTTPUnsupportedMediaType",
|
|
"HTTPRequestRangeNotSatisfiable",
|
|
"HTTPExpectationFailed",
|
|
"HTTPMisdirectedRequest",
|
|
"HTTPUnprocessableEntity",
|
|
"HTTPFailedDependency",
|
|
"HTTPUpgradeRequired",
|
|
"HTTPPreconditionRequired",
|
|
"HTTPTooManyRequests",
|
|
"HTTPRequestHeaderFieldsTooLarge",
|
|
"HTTPUnavailableForLegalReasons",
|
|
"HTTPServerError",
|
|
"HTTPInternalServerError",
|
|
"HTTPNotImplemented",
|
|
"HTTPBadGateway",
|
|
"HTTPServiceUnavailable",
|
|
"HTTPGatewayTimeout",
|
|
"HTTPVersionNotSupported",
|
|
"HTTPVariantAlsoNegotiates",
|
|
"HTTPInsufficientStorage",
|
|
"HTTPNotExtended",
|
|
"HTTPNetworkAuthenticationRequired",
|
|
)
|
|
|
|
|
|
class NotAppKeyWarning(UserWarning):
|
|
"""Warning when not using AppKey in Application."""
|
|
|
|
|
|
############################################################
|
|
# HTTP Exceptions
|
|
############################################################
|
|
|
|
|
|
class HTTPException(Response, Exception):
|
|
|
|
# You should set in subclasses:
|
|
# status = 200
|
|
|
|
status_code = -1
|
|
empty_body = False
|
|
|
|
__http_exception__ = True
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
headers: Optional[LooseHeaders] = None,
|
|
reason: Optional[str] = None,
|
|
body: Any = None,
|
|
text: Optional[str] = None,
|
|
content_type: Optional[str] = None,
|
|
) -> None:
|
|
if body is not None:
|
|
warnings.warn(
|
|
"body argument is deprecated for http web exceptions",
|
|
DeprecationWarning,
|
|
)
|
|
Response.__init__(
|
|
self,
|
|
status=self.status_code,
|
|
headers=headers,
|
|
reason=reason,
|
|
body=body,
|
|
text=text,
|
|
content_type=content_type,
|
|
)
|
|
Exception.__init__(self, self.reason)
|
|
if self.body is None and not self.empty_body:
|
|
self.text = f"{self.status}: {self.reason}"
|
|
|
|
def __bool__(self) -> bool:
|
|
return True
|
|
|
|
|
|
class HTTPError(HTTPException):
|
|
"""Base class for exceptions with status codes in the 400s and 500s."""
|
|
|
|
|
|
class HTTPRedirection(HTTPException):
|
|
"""Base class for exceptions with status codes in the 300s."""
|
|
|
|
|
|
class HTTPSuccessful(HTTPException):
|
|
"""Base class for exceptions with status codes in the 200s."""
|
|
|
|
|
|
class HTTPOk(HTTPSuccessful):
|
|
status_code = 200
|
|
|
|
|
|
class HTTPCreated(HTTPSuccessful):
|
|
status_code = 201
|
|
|
|
|
|
class HTTPAccepted(HTTPSuccessful):
|
|
status_code = 202
|
|
|
|
|
|
class HTTPNonAuthoritativeInformation(HTTPSuccessful):
|
|
status_code = 203
|
|
|
|
|
|
class HTTPNoContent(HTTPSuccessful):
|
|
status_code = 204
|
|
empty_body = True
|
|
|
|
|
|
class HTTPResetContent(HTTPSuccessful):
|
|
status_code = 205
|
|
empty_body = True
|
|
|
|
|
|
class HTTPPartialContent(HTTPSuccessful):
|
|
status_code = 206
|
|
|
|
|
|
############################################################
|
|
# 3xx redirection
|
|
############################################################
|
|
|
|
|
|
class HTTPMove(HTTPRedirection):
|
|
def __init__(
|
|
self,
|
|
location: StrOrURL,
|
|
*,
|
|
headers: Optional[LooseHeaders] = None,
|
|
reason: Optional[str] = None,
|
|
body: Any = None,
|
|
text: Optional[str] = None,
|
|
content_type: Optional[str] = None,
|
|
) -> None:
|
|
if not location:
|
|
raise ValueError("HTTP redirects need a location to redirect to.")
|
|
super().__init__(
|
|
headers=headers,
|
|
reason=reason,
|
|
body=body,
|
|
text=text,
|
|
content_type=content_type,
|
|
)
|
|
self.headers["Location"] = str(URL(location))
|
|
self.location = location
|
|
|
|
|
|
class HTTPMultipleChoices(HTTPMove):
|
|
status_code = 300
|
|
|
|
|
|
class HTTPMovedPermanently(HTTPMove):
|
|
status_code = 301
|
|
|
|
|
|
class HTTPFound(HTTPMove):
|
|
status_code = 302
|
|
|
|
|
|
# This one is safe after a POST (the redirected location will be
|
|
# retrieved with GET):
|
|
class HTTPSeeOther(HTTPMove):
|
|
status_code = 303
|
|
|
|
|
|
class HTTPNotModified(HTTPRedirection):
|
|
# FIXME: this should include a date or etag header
|
|
status_code = 304
|
|
empty_body = True
|
|
|
|
|
|
class HTTPUseProxy(HTTPMove):
|
|
# Not a move, but looks a little like one
|
|
status_code = 305
|
|
|
|
|
|
class HTTPTemporaryRedirect(HTTPMove):
|
|
status_code = 307
|
|
|
|
|
|
class HTTPPermanentRedirect(HTTPMove):
|
|
status_code = 308
|
|
|
|
|
|
############################################################
|
|
# 4xx client error
|
|
############################################################
|
|
|
|
|
|
class HTTPClientError(HTTPError):
|
|
pass
|
|
|
|
|
|
class HTTPBadRequest(HTTPClientError):
|
|
status_code = 400
|
|
|
|
|
|
class HTTPUnauthorized(HTTPClientError):
|
|
status_code = 401
|
|
|
|
|
|
class HTTPPaymentRequired(HTTPClientError):
|
|
status_code = 402
|
|
|
|
|
|
class HTTPForbidden(HTTPClientError):
|
|
status_code = 403
|
|
|
|
|
|
class HTTPNotFound(HTTPClientError):
|
|
status_code = 404
|
|
|
|
|
|
class HTTPMethodNotAllowed(HTTPClientError):
|
|
status_code = 405
|
|
|
|
def __init__(
|
|
self,
|
|
method: str,
|
|
allowed_methods: Iterable[str],
|
|
*,
|
|
headers: Optional[LooseHeaders] = None,
|
|
reason: Optional[str] = None,
|
|
body: Any = None,
|
|
text: Optional[str] = None,
|
|
content_type: Optional[str] = None,
|
|
) -> None:
|
|
allow = ",".join(sorted(allowed_methods))
|
|
super().__init__(
|
|
headers=headers,
|
|
reason=reason,
|
|
body=body,
|
|
text=text,
|
|
content_type=content_type,
|
|
)
|
|
self.headers["Allow"] = allow
|
|
self.allowed_methods: Set[str] = set(allowed_methods)
|
|
self.method = method.upper()
|
|
|
|
|
|
class HTTPNotAcceptable(HTTPClientError):
|
|
status_code = 406
|
|
|
|
|
|
class HTTPProxyAuthenticationRequired(HTTPClientError):
|
|
status_code = 407
|
|
|
|
|
|
class HTTPRequestTimeout(HTTPClientError):
|
|
status_code = 408
|
|
|
|
|
|
class HTTPConflict(HTTPClientError):
|
|
status_code = 409
|
|
|
|
|
|
class HTTPGone(HTTPClientError):
|
|
status_code = 410
|
|
|
|
|
|
class HTTPLengthRequired(HTTPClientError):
|
|
status_code = 411
|
|
|
|
|
|
class HTTPPreconditionFailed(HTTPClientError):
|
|
status_code = 412
|
|
|
|
|
|
class HTTPRequestEntityTooLarge(HTTPClientError):
|
|
status_code = 413
|
|
|
|
def __init__(self, max_size: float, actual_size: float, **kwargs: Any) -> None:
|
|
kwargs.setdefault(
|
|
"text",
|
|
"Maximum request body size {} exceeded, "
|
|
"actual body size {}".format(max_size, actual_size),
|
|
)
|
|
super().__init__(**kwargs)
|
|
|
|
|
|
class HTTPRequestURITooLong(HTTPClientError):
|
|
status_code = 414
|
|
|
|
|
|
class HTTPUnsupportedMediaType(HTTPClientError):
|
|
status_code = 415
|
|
|
|
|
|
class HTTPRequestRangeNotSatisfiable(HTTPClientError):
|
|
status_code = 416
|
|
|
|
|
|
class HTTPExpectationFailed(HTTPClientError):
|
|
status_code = 417
|
|
|
|
|
|
class HTTPMisdirectedRequest(HTTPClientError):
|
|
status_code = 421
|
|
|
|
|
|
class HTTPUnprocessableEntity(HTTPClientError):
|
|
status_code = 422
|
|
|
|
|
|
class HTTPFailedDependency(HTTPClientError):
|
|
status_code = 424
|
|
|
|
|
|
class HTTPUpgradeRequired(HTTPClientError):
|
|
status_code = 426
|
|
|
|
|
|
class HTTPPreconditionRequired(HTTPClientError):
|
|
status_code = 428
|
|
|
|
|
|
class HTTPTooManyRequests(HTTPClientError):
|
|
status_code = 429
|
|
|
|
|
|
class HTTPRequestHeaderFieldsTooLarge(HTTPClientError):
|
|
status_code = 431
|
|
|
|
|
|
class HTTPUnavailableForLegalReasons(HTTPClientError):
|
|
status_code = 451
|
|
|
|
def __init__(
|
|
self,
|
|
link: Optional[StrOrURL],
|
|
*,
|
|
headers: Optional[LooseHeaders] = None,
|
|
reason: Optional[str] = None,
|
|
body: Any = None,
|
|
text: Optional[str] = None,
|
|
content_type: Optional[str] = None,
|
|
) -> None:
|
|
super().__init__(
|
|
headers=headers,
|
|
reason=reason,
|
|
body=body,
|
|
text=text,
|
|
content_type=content_type,
|
|
)
|
|
self._link = None
|
|
if link:
|
|
self._link = URL(link)
|
|
self.headers["Link"] = f'<{str(self._link)}>; rel="blocked-by"'
|
|
|
|
@property
|
|
def link(self) -> Optional[URL]:
|
|
return self._link
|
|
|
|
|
|
############################################################
|
|
# 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):
|
|
pass
|
|
|
|
|
|
class HTTPInternalServerError(HTTPServerError):
|
|
status_code = 500
|
|
|
|
|
|
class HTTPNotImplemented(HTTPServerError):
|
|
status_code = 501
|
|
|
|
|
|
class HTTPBadGateway(HTTPServerError):
|
|
status_code = 502
|
|
|
|
|
|
class HTTPServiceUnavailable(HTTPServerError):
|
|
status_code = 503
|
|
|
|
|
|
class HTTPGatewayTimeout(HTTPServerError):
|
|
status_code = 504
|
|
|
|
|
|
class HTTPVersionNotSupported(HTTPServerError):
|
|
status_code = 505
|
|
|
|
|
|
class HTTPVariantAlsoNegotiates(HTTPServerError):
|
|
status_code = 506
|
|
|
|
|
|
class HTTPInsufficientStorage(HTTPServerError):
|
|
status_code = 507
|
|
|
|
|
|
class HTTPNotExtended(HTTPServerError):
|
|
status_code = 510
|
|
|
|
|
|
class HTTPNetworkAuthenticationRequired(HTTPServerError):
|
|
status_code = 511
|