client_exceptions.py 9.1 KB

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