client_exceptions.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. """HTTP related errors."""
  2. import asyncio
  3. import warnings
  4. from typing import TYPE_CHECKING, Optional, Tuple, Union
  5. from multidict import MultiMapping
  6. from .typedefs import StrOrURL
  7. if TYPE_CHECKING:
  8. import ssl
  9. SSLContext = ssl.SSLContext
  10. else:
  11. try:
  12. import ssl
  13. SSLContext = ssl.SSLContext
  14. except ImportError: # pragma: no cover
  15. ssl = SSLContext = None # type: ignore[assignment]
  16. if TYPE_CHECKING:
  17. from .client_reqrep import ClientResponse, ConnectionKey, Fingerprint, RequestInfo
  18. from .http_parser import RawResponseMessage
  19. else:
  20. RequestInfo = ClientResponse = ConnectionKey = RawResponseMessage = None
  21. __all__ = (
  22. "ClientError",
  23. "ClientConnectionError",
  24. "ClientConnectionResetError",
  25. "ClientOSError",
  26. "ClientConnectorError",
  27. "ClientProxyConnectionError",
  28. "ClientSSLError",
  29. "ClientConnectorDNSError",
  30. "ClientConnectorSSLError",
  31. "ClientConnectorCertificateError",
  32. "ConnectionTimeoutError",
  33. "SocketTimeoutError",
  34. "ServerConnectionError",
  35. "ServerTimeoutError",
  36. "ServerDisconnectedError",
  37. "ServerFingerprintMismatch",
  38. "ClientResponseError",
  39. "ClientHttpProxyError",
  40. "WSServerHandshakeError",
  41. "ContentTypeError",
  42. "ClientPayloadError",
  43. "InvalidURL",
  44. "InvalidUrlClientError",
  45. "RedirectClientError",
  46. "NonHttpUrlClientError",
  47. "InvalidUrlRedirectClientError",
  48. "NonHttpUrlRedirectClientError",
  49. "WSMessageTypeError",
  50. )
  51. class ClientError(Exception):
  52. """Base class for client connection errors."""
  53. class ClientResponseError(ClientError):
  54. """Base class for exceptions that occur after getting a response.
  55. request_info: An instance of RequestInfo.
  56. history: A sequence of responses, if redirects occurred.
  57. status: HTTP status code.
  58. message: Error message.
  59. headers: Response headers.
  60. """
  61. def __init__(
  62. self,
  63. request_info: RequestInfo,
  64. history: Tuple[ClientResponse, ...],
  65. *,
  66. code: Optional[int] = None,
  67. status: Optional[int] = None,
  68. message: str = "",
  69. headers: Optional[MultiMapping[str]] = None,
  70. ) -> None:
  71. self.request_info = request_info
  72. if code is not None:
  73. if status is not None:
  74. raise ValueError(
  75. "Both code and status arguments are provided; "
  76. "code is deprecated, use status instead"
  77. )
  78. warnings.warn(
  79. "code argument is deprecated, use status instead",
  80. DeprecationWarning,
  81. stacklevel=2,
  82. )
  83. if status is not None:
  84. self.status = status
  85. elif code is not None:
  86. self.status = code
  87. else:
  88. self.status = 0
  89. self.message = message
  90. self.headers = headers
  91. self.history = history
  92. self.args = (request_info, history)
  93. def __str__(self) -> str:
  94. return "{}, message={!r}, url={!r}".format(
  95. self.status,
  96. self.message,
  97. str(self.request_info.real_url),
  98. )
  99. def __repr__(self) -> str:
  100. args = f"{self.request_info!r}, {self.history!r}"
  101. if self.status != 0:
  102. args += f", status={self.status!r}"
  103. if self.message != "":
  104. args += f", message={self.message!r}"
  105. if self.headers is not None:
  106. args += f", headers={self.headers!r}"
  107. return f"{type(self).__name__}({args})"
  108. @property
  109. def code(self) -> int:
  110. warnings.warn(
  111. "code property is deprecated, use status instead",
  112. DeprecationWarning,
  113. stacklevel=2,
  114. )
  115. return self.status
  116. @code.setter
  117. def code(self, value: int) -> None:
  118. warnings.warn(
  119. "code property is deprecated, use status instead",
  120. DeprecationWarning,
  121. stacklevel=2,
  122. )
  123. self.status = value
  124. class ContentTypeError(ClientResponseError):
  125. """ContentType found is not valid."""
  126. class WSServerHandshakeError(ClientResponseError):
  127. """websocket server handshake error."""
  128. class ClientHttpProxyError(ClientResponseError):
  129. """HTTP proxy error.
  130. Raised in :class:`aiohttp.connector.TCPConnector` if
  131. proxy responds with status other than ``200 OK``
  132. on ``CONNECT`` request.
  133. """
  134. class TooManyRedirects(ClientResponseError):
  135. """Client was redirected too many times."""
  136. class ClientConnectionError(ClientError):
  137. """Base class for client socket errors."""
  138. class ClientConnectionResetError(ClientConnectionError, ConnectionResetError):
  139. """ConnectionResetError"""
  140. class ClientOSError(ClientConnectionError, OSError):
  141. """OSError error."""
  142. class ClientConnectorError(ClientOSError):
  143. """Client connector error.
  144. Raised in :class:`aiohttp.connector.TCPConnector` if
  145. a connection can not be established.
  146. """
  147. def __init__(self, connection_key: ConnectionKey, os_error: OSError) -> None:
  148. self._conn_key = connection_key
  149. self._os_error = os_error
  150. super().__init__(os_error.errno, os_error.strerror)
  151. self.args = (connection_key, os_error)
  152. @property
  153. def os_error(self) -> OSError:
  154. return self._os_error
  155. @property
  156. def host(self) -> str:
  157. return self._conn_key.host
  158. @property
  159. def port(self) -> Optional[int]:
  160. return self._conn_key.port
  161. @property
  162. def ssl(self) -> Union[SSLContext, bool, "Fingerprint"]:
  163. return self._conn_key.ssl
  164. def __str__(self) -> str:
  165. return "Cannot connect to host {0.host}:{0.port} ssl:{1} [{2}]".format(
  166. self, "default" if self.ssl is True else self.ssl, self.strerror
  167. )
  168. # OSError.__reduce__ does too much black magick
  169. __reduce__ = BaseException.__reduce__
  170. class ClientConnectorDNSError(ClientConnectorError):
  171. """DNS resolution failed during client connection.
  172. Raised in :class:`aiohttp.connector.TCPConnector` if
  173. DNS resolution fails.
  174. """
  175. class ClientProxyConnectionError(ClientConnectorError):
  176. """Proxy connection error.
  177. Raised in :class:`aiohttp.connector.TCPConnector` if
  178. connection to proxy can not be established.
  179. """
  180. class UnixClientConnectorError(ClientConnectorError):
  181. """Unix connector error.
  182. Raised in :py:class:`aiohttp.connector.UnixConnector`
  183. if connection to unix socket can not be established.
  184. """
  185. def __init__(
  186. self, path: str, connection_key: ConnectionKey, os_error: OSError
  187. ) -> None:
  188. self._path = path
  189. super().__init__(connection_key, os_error)
  190. @property
  191. def path(self) -> str:
  192. return self._path
  193. def __str__(self) -> str:
  194. return "Cannot connect to unix socket {0.path} ssl:{1} [{2}]".format(
  195. self, "default" if self.ssl is True else self.ssl, self.strerror
  196. )
  197. class ServerConnectionError(ClientConnectionError):
  198. """Server connection errors."""
  199. class ServerDisconnectedError(ServerConnectionError):
  200. """Server disconnected."""
  201. def __init__(self, message: Union[RawResponseMessage, str, None] = None) -> None:
  202. if message is None:
  203. message = "Server disconnected"
  204. self.args = (message,)
  205. self.message = message
  206. class ServerTimeoutError(ServerConnectionError, asyncio.TimeoutError):
  207. """Server timeout error."""
  208. class ConnectionTimeoutError(ServerTimeoutError):
  209. """Connection timeout error."""
  210. class SocketTimeoutError(ServerTimeoutError):
  211. """Socket timeout error."""
  212. class ServerFingerprintMismatch(ServerConnectionError):
  213. """SSL certificate does not match expected fingerprint."""
  214. def __init__(self, expected: bytes, got: bytes, host: str, port: int) -> None:
  215. self.expected = expected
  216. self.got = got
  217. self.host = host
  218. self.port = port
  219. self.args = (expected, got, host, port)
  220. def __repr__(self) -> str:
  221. return "<{} expected={!r} got={!r} host={!r} port={!r}>".format(
  222. self.__class__.__name__, self.expected, self.got, self.host, self.port
  223. )
  224. class ClientPayloadError(ClientError):
  225. """Response payload error."""
  226. class InvalidURL(ClientError, ValueError):
  227. """Invalid URL.
  228. URL used for fetching is malformed, e.g. it doesn't contains host
  229. part.
  230. """
  231. # Derive from ValueError for backward compatibility
  232. def __init__(self, url: StrOrURL, description: Union[str, None] = None) -> None:
  233. # The type of url is not yarl.URL because the exception can be raised
  234. # on URL(url) call
  235. self._url = url
  236. self._description = description
  237. if description:
  238. super().__init__(url, description)
  239. else:
  240. super().__init__(url)
  241. @property
  242. def url(self) -> StrOrURL:
  243. return self._url
  244. @property
  245. def description(self) -> "str | None":
  246. return self._description
  247. def __repr__(self) -> str:
  248. return f"<{self.__class__.__name__} {self}>"
  249. def __str__(self) -> str:
  250. if self._description:
  251. return f"{self._url} - {self._description}"
  252. return str(self._url)
  253. class InvalidUrlClientError(InvalidURL):
  254. """Invalid URL client error."""
  255. class RedirectClientError(ClientError):
  256. """Client redirect error."""
  257. class NonHttpUrlClientError(ClientError):
  258. """Non http URL client error."""
  259. class InvalidUrlRedirectClientError(InvalidUrlClientError, RedirectClientError):
  260. """Invalid URL redirect client error."""
  261. class NonHttpUrlRedirectClientError(NonHttpUrlClientError, RedirectClientError):
  262. """Non http URL redirect client error."""
  263. class ClientSSLError(ClientConnectorError):
  264. """Base error for ssl.*Errors."""
  265. if ssl is not None:
  266. cert_errors = (ssl.CertificateError,)
  267. cert_errors_bases = (
  268. ClientSSLError,
  269. ssl.CertificateError,
  270. )
  271. ssl_errors = (ssl.SSLError,)
  272. ssl_error_bases = (ClientSSLError, ssl.SSLError)
  273. else: # pragma: no cover
  274. cert_errors = tuple()
  275. cert_errors_bases = (
  276. ClientSSLError,
  277. ValueError,
  278. )
  279. ssl_errors = tuple()
  280. ssl_error_bases = (ClientSSLError,)
  281. class ClientConnectorSSLError(*ssl_error_bases): # type: ignore[misc]
  282. """Response ssl error."""
  283. class ClientConnectorCertificateError(*cert_errors_bases): # type: ignore[misc]
  284. """Response certificate error."""
  285. def __init__(
  286. self, connection_key: ConnectionKey, certificate_error: Exception
  287. ) -> None:
  288. self._conn_key = connection_key
  289. self._certificate_error = certificate_error
  290. self.args = (connection_key, certificate_error)
  291. @property
  292. def certificate_error(self) -> Exception:
  293. return self._certificate_error
  294. @property
  295. def host(self) -> str:
  296. return self._conn_key.host
  297. @property
  298. def port(self) -> Optional[int]:
  299. return self._conn_key.port
  300. @property
  301. def ssl(self) -> bool:
  302. return self._conn_key.is_ssl
  303. def __str__(self) -> str:
  304. return (
  305. "Cannot connect to host {0.host}:{0.port} ssl:{0.ssl} "
  306. "[{0.certificate_error.__class__.__name__}: "
  307. "{0.certificate_error.args}]".format(self)
  308. )
  309. class WSMessageTypeError(TypeError):
  310. """WebSocket message type is not valid."""