293 lines
8.3 KiB
Python
293 lines
8.3 KiB
Python
|
"""HTTP related errors."""
|
||
|
|
||
|
import asyncio
|
||
|
import warnings
|
||
|
from typing import TYPE_CHECKING, Any, Optional, Tuple, Union
|
||
|
|
||
|
from .typedefs import _CIMultiDict
|
||
|
|
||
|
try:
|
||
|
import ssl
|
||
|
SSLContext = ssl.SSLContext
|
||
|
except ImportError: # pragma: no cover
|
||
|
ssl = SSLContext = None # type: ignore
|
||
|
|
||
|
|
||
|
if TYPE_CHECKING: # pragma: no cover
|
||
|
from .client_reqrep import (RequestInfo, ClientResponse, ConnectionKey, # noqa
|
||
|
Fingerprint)
|
||
|
else:
|
||
|
RequestInfo = ClientResponse = ConnectionKey = None
|
||
|
|
||
|
|
||
|
__all__ = (
|
||
|
'ClientError',
|
||
|
|
||
|
'ClientConnectionError',
|
||
|
'ClientOSError', 'ClientConnectorError', 'ClientProxyConnectionError',
|
||
|
|
||
|
'ClientSSLError',
|
||
|
'ClientConnectorSSLError', 'ClientConnectorCertificateError',
|
||
|
|
||
|
'ServerConnectionError', 'ServerTimeoutError', 'ServerDisconnectedError',
|
||
|
'ServerFingerprintMismatch',
|
||
|
|
||
|
'ClientResponseError', 'ClientHttpProxyError',
|
||
|
'WSServerHandshakeError', 'ContentTypeError',
|
||
|
|
||
|
'ClientPayloadError', 'InvalidURL')
|
||
|
|
||
|
|
||
|
class ClientError(Exception):
|
||
|
"""Base class for client connection errors."""
|
||
|
|
||
|
|
||
|
class ClientResponseError(ClientError):
|
||
|
"""Connection error during reading response.
|
||
|
|
||
|
request_info: instance of RequestInfo
|
||
|
"""
|
||
|
|
||
|
def __init__(self, request_info: RequestInfo,
|
||
|
history: Tuple[ClientResponse, ...], *,
|
||
|
code: Optional[int]=None,
|
||
|
status: Optional[int]=None,
|
||
|
message: str='',
|
||
|
headers: Optional[_CIMultiDict]=None) -> None:
|
||
|
self.request_info = request_info
|
||
|
if code is not None:
|
||
|
if status is not None:
|
||
|
raise ValueError(
|
||
|
"Both code and status arguments are provided; "
|
||
|
"code is deprecated, use status instead")
|
||
|
warnings.warn("code argument is deprecated, use status instead",
|
||
|
DeprecationWarning,
|
||
|
stacklevel=2)
|
||
|
if status is not None:
|
||
|
self.status = status
|
||
|
elif code is not None:
|
||
|
self.status = code
|
||
|
else:
|
||
|
self.status = 0
|
||
|
self.message = message
|
||
|
self.headers = headers
|
||
|
self.history = history
|
||
|
self.args = (request_info, history)
|
||
|
|
||
|
def __str__(self) -> str:
|
||
|
return ("%s, message=%r, url=%r" %
|
||
|
(self.status, self.message, self.request_info.real_url))
|
||
|
|
||
|
def __repr__(self) -> str:
|
||
|
args = "%r, %r" % (self.request_info, self.history)
|
||
|
if self.status != 0:
|
||
|
args += ", status=%r" % (self.status,)
|
||
|
if self.message != '':
|
||
|
args += ", message=%r" % (self.message,)
|
||
|
if self.headers is not None:
|
||
|
args += ", headers=%r" % (self.headers,)
|
||
|
return "%s(%s)" % (type(self).__name__, args)
|
||
|
|
||
|
@property
|
||
|
def code(self) -> int:
|
||
|
warnings.warn("code property is deprecated, use status instead",
|
||
|
DeprecationWarning,
|
||
|
stacklevel=2)
|
||
|
return self.status
|
||
|
|
||
|
@code.setter
|
||
|
def code(self, value: int) -> None:
|
||
|
warnings.warn("code property is deprecated, use status instead",
|
||
|
DeprecationWarning,
|
||
|
stacklevel=2)
|
||
|
self.status = value
|
||
|
|
||
|
|
||
|
class ContentTypeError(ClientResponseError):
|
||
|
"""ContentType found is not valid."""
|
||
|
|
||
|
|
||
|
class WSServerHandshakeError(ClientResponseError):
|
||
|
"""websocket server handshake error."""
|
||
|
|
||
|
|
||
|
class ClientHttpProxyError(ClientResponseError):
|
||
|
"""HTTP proxy error.
|
||
|
|
||
|
Raised in :class:`aiohttp.connector.TCPConnector` if
|
||
|
proxy responds with status other than ``200 OK``
|
||
|
on ``CONNECT`` request.
|
||
|
"""
|
||
|
|
||
|
|
||
|
class TooManyRedirects(ClientResponseError):
|
||
|
"""Client was redirected too many times."""
|
||
|
|
||
|
|
||
|
class ClientConnectionError(ClientError):
|
||
|
"""Base class for client socket errors."""
|
||
|
|
||
|
|
||
|
class ClientOSError(ClientConnectionError, OSError):
|
||
|
"""OSError error."""
|
||
|
|
||
|
|
||
|
class ClientConnectorError(ClientOSError):
|
||
|
"""Client connector error.
|
||
|
|
||
|
Raised in :class:`aiohttp.connector.TCPConnector` if
|
||
|
connection to proxy can not be established.
|
||
|
"""
|
||
|
def __init__(self, connection_key: ConnectionKey,
|
||
|
os_error: OSError) -> None:
|
||
|
self._conn_key = connection_key
|
||
|
self._os_error = os_error
|
||
|
super().__init__(os_error.errno, os_error.strerror)
|
||
|
self.args = (connection_key, os_error)
|
||
|
|
||
|
@property
|
||
|
def os_error(self) -> OSError:
|
||
|
return self._os_error
|
||
|
|
||
|
@property
|
||
|
def host(self) -> str:
|
||
|
return self._conn_key.host
|
||
|
|
||
|
@property
|
||
|
def port(self) -> Optional[int]:
|
||
|
return self._conn_key.port
|
||
|
|
||
|
@property
|
||
|
def ssl(self) -> Union[SSLContext, None, bool, 'Fingerprint']:
|
||
|
return self._conn_key.ssl
|
||
|
|
||
|
def __str__(self) -> str:
|
||
|
return ('Cannot connect to host {0.host}:{0.port} ssl:{1} [{2}]'
|
||
|
.format(self, self.ssl if self.ssl is not None else 'default',
|
||
|
self.strerror))
|
||
|
|
||
|
# OSError.__reduce__ does too much black magick
|
||
|
__reduce__ = BaseException.__reduce__
|
||
|
|
||
|
|
||
|
class ClientProxyConnectionError(ClientConnectorError):
|
||
|
"""Proxy connection error.
|
||
|
|
||
|
Raised in :class:`aiohttp.connector.TCPConnector` if
|
||
|
connection to proxy can not be established.
|
||
|
"""
|
||
|
|
||
|
|
||
|
class ServerConnectionError(ClientConnectionError):
|
||
|
"""Server connection errors."""
|
||
|
|
||
|
|
||
|
class ServerDisconnectedError(ServerConnectionError):
|
||
|
"""Server disconnected."""
|
||
|
|
||
|
def __init__(self, message: Optional[str]=None) -> None:
|
||
|
self.message = message
|
||
|
if message is None:
|
||
|
self.args = ()
|
||
|
else:
|
||
|
self.args = (message,)
|
||
|
|
||
|
|
||
|
class ServerTimeoutError(ServerConnectionError, asyncio.TimeoutError):
|
||
|
"""Server timeout error."""
|
||
|
|
||
|
|
||
|
class ServerFingerprintMismatch(ServerConnectionError):
|
||
|
"""SSL certificate does not match expected fingerprint."""
|
||
|
|
||
|
def __init__(self, expected: bytes, got: bytes,
|
||
|
host: str, port: int) -> None:
|
||
|
self.expected = expected
|
||
|
self.got = got
|
||
|
self.host = host
|
||
|
self.port = port
|
||
|
self.args = (expected, got, host, port)
|
||
|
|
||
|
def __repr__(self) -> str:
|
||
|
return '<{} expected={!r} got={!r} host={!r} port={!r}>'.format(
|
||
|
self.__class__.__name__, self.expected, self.got,
|
||
|
self.host, self.port)
|
||
|
|
||
|
|
||
|
class ClientPayloadError(ClientError):
|
||
|
"""Response payload error."""
|
||
|
|
||
|
|
||
|
class InvalidURL(ClientError, ValueError):
|
||
|
"""Invalid URL.
|
||
|
|
||
|
URL used for fetching is malformed, e.g. it doesn't contains host
|
||
|
part."""
|
||
|
|
||
|
# Derive from ValueError for backward compatibility
|
||
|
|
||
|
def __init__(self, url: Any) -> None:
|
||
|
# The type of url is not yarl.URL because the exception can be raised
|
||
|
# on URL(url) call
|
||
|
super().__init__(url)
|
||
|
|
||
|
@property
|
||
|
def url(self) -> Any:
|
||
|
return self.args[0]
|
||
|
|
||
|
def __repr__(self) -> str:
|
||
|
return '<{} {}>'.format(self.__class__.__name__, self.url)
|
||
|
|
||
|
|
||
|
class ClientSSLError(ClientConnectorError):
|
||
|
"""Base error for ssl.*Errors."""
|
||
|
|
||
|
|
||
|
if ssl is not None:
|
||
|
cert_errors = (ssl.CertificateError,)
|
||
|
cert_errors_bases = (ClientSSLError, ssl.CertificateError,)
|
||
|
|
||
|
ssl_errors = (ssl.SSLError,)
|
||
|
ssl_error_bases = (ClientSSLError, ssl.SSLError)
|
||
|
else: # pragma: no cover
|
||
|
cert_errors = tuple()
|
||
|
cert_errors_bases = (ClientSSLError, ValueError,)
|
||
|
|
||
|
ssl_errors = tuple()
|
||
|
ssl_error_bases = (ClientSSLError,)
|
||
|
|
||
|
|
||
|
class ClientConnectorSSLError(*ssl_error_bases): # type: ignore
|
||
|
"""Response ssl error."""
|
||
|
|
||
|
|
||
|
class ClientConnectorCertificateError(*cert_errors_bases): # type: ignore
|
||
|
"""Response certificate error."""
|
||
|
|
||
|
def __init__(self, connection_key:
|
||
|
ConnectionKey, certificate_error: Exception) -> None:
|
||
|
self._conn_key = connection_key
|
||
|
self._certificate_error = certificate_error
|
||
|
self.args = (connection_key, certificate_error)
|
||
|
|
||
|
@property
|
||
|
def certificate_error(self) -> Exception:
|
||
|
return self._certificate_error
|
||
|
|
||
|
@property
|
||
|
def host(self) -> str:
|
||
|
return self._conn_key.host
|
||
|
|
||
|
@property
|
||
|
def port(self) -> Optional[int]:
|
||
|
return self._conn_key.port
|
||
|
|
||
|
@property
|
||
|
def ssl(self) -> bool:
|
||
|
return self._conn_key.is_ssl
|
||
|
|
||
|
def __str__(self) -> str:
|
||
|
return ('Cannot connect to host {0.host}:{0.port} ssl:{0.ssl} '
|
||
|
'[{0.certificate_error.__class__.__name__}: '
|
||
|
'{0.certificate_error.args}]'.format(self))
|