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