client_exceptions.py 8.3 KB

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