1"""
2:mod:`websockets.exceptions` defines the following hierarchy of exceptions.
3
4* :exc:`WebSocketException`
5 * :exc:`ConnectionClosed`
6 * :exc:`ConnectionClosedOK`
7 * :exc:`ConnectionClosedError`
8 * :exc:`InvalidURI`
9 * :exc:`InvalidProxy`
10 * :exc:`InvalidHandshake`
11 * :exc:`SecurityError`
12 * :exc:`ProxyError`
13 * :exc:`InvalidProxyMessage`
14 * :exc:`InvalidProxyStatus`
15 * :exc:`InvalidMessage`
16 * :exc:`InvalidStatus`
17 * :exc:`InvalidStatusCode` (legacy)
18 * :exc:`InvalidHeader`
19 * :exc:`InvalidHeaderFormat`
20 * :exc:`InvalidHeaderValue`
21 * :exc:`InvalidOrigin`
22 * :exc:`InvalidUpgrade`
23 * :exc:`NegotiationError`
24 * :exc:`DuplicateParameter`
25 * :exc:`InvalidParameterName`
26 * :exc:`InvalidParameterValue`
27 * :exc:`AbortHandshake` (legacy)
28 * :exc:`RedirectHandshake` (legacy)
29 * :exc:`ProtocolError` (Sans-I/O)
30 * :exc:`PayloadTooBig` (Sans-I/O)
31 * :exc:`InvalidState` (Sans-I/O)
32 * :exc:`ConcurrencyError`
33
34"""
35
36from __future__ import annotations
37
38import warnings
39
40from .imports import lazy_import
41
42
43__all__ = [
44 "WebSocketException",
45 "ConnectionClosed",
46 "ConnectionClosedOK",
47 "ConnectionClosedError",
48 "InvalidURI",
49 "InvalidProxy",
50 "InvalidHandshake",
51 "SecurityError",
52 "ProxyError",
53 "InvalidProxyMessage",
54 "InvalidProxyStatus",
55 "InvalidMessage",
56 "InvalidStatus",
57 "InvalidHeader",
58 "InvalidHeaderFormat",
59 "InvalidHeaderValue",
60 "InvalidOrigin",
61 "InvalidUpgrade",
62 "NegotiationError",
63 "DuplicateParameter",
64 "InvalidParameterName",
65 "InvalidParameterValue",
66 "ProtocolError",
67 "PayloadTooBig",
68 "InvalidState",
69 "ConcurrencyError",
70]
71
72
73class WebSocketException(Exception):
74 """
75 Base class for all exceptions defined by websockets.
76
77 """
78
79
80class ConnectionClosed(WebSocketException):
81 """
82 Raised when trying to interact with a closed connection.
83
84 Attributes:
85 rcvd: If a close frame was received, its code and reason are available
86 in ``rcvd.code`` and ``rcvd.reason``.
87 sent: If a close frame was sent, its code and reason are available
88 in ``sent.code`` and ``sent.reason``.
89 rcvd_then_sent: If close frames were received and sent, this attribute
90 tells in which order this happened, from the perspective of this
91 side of the connection.
92
93 """
94
95 def __init__(
96 self,
97 rcvd: frames.Close | None,
98 sent: frames.Close | None,
99 rcvd_then_sent: bool | None = None,
100 ) -> None:
101 self.rcvd = rcvd
102 self.sent = sent
103 self.rcvd_then_sent = rcvd_then_sent
104 assert (self.rcvd_then_sent is None) == (self.rcvd is None or self.sent is None)
105
106 def __str__(self) -> str:
107 if self.rcvd is None:
108 if self.sent is None:
109 return "no close frame received or sent"
110 else:
111 return f"sent {self.sent}; no close frame received"
112 else:
113 if self.sent is None:
114 return f"received {self.rcvd}; no close frame sent"
115 else:
116 if self.rcvd_then_sent:
117 return f"received {self.rcvd}; then sent {self.sent}"
118 else:
119 return f"sent {self.sent}; then received {self.rcvd}"
120
121 # code and reason attributes are provided for backwards-compatibility
122
123 @property
124 def code(self) -> int:
125 warnings.warn( # deprecated in 13.1 - 2024-09-21
126 "ConnectionClosed.code is deprecated; "
127 "use Protocol.close_code or ConnectionClosed.rcvd.code",
128 DeprecationWarning,
129 )
130 if self.rcvd is None:
131 return frames.CloseCode.ABNORMAL_CLOSURE
132 return self.rcvd.code
133
134 @property
135 def reason(self) -> str:
136 warnings.warn( # deprecated in 13.1 - 2024-09-21
137 "ConnectionClosed.reason is deprecated; "
138 "use Protocol.close_reason or ConnectionClosed.rcvd.reason",
139 DeprecationWarning,
140 )
141 if self.rcvd is None:
142 return ""
143 return self.rcvd.reason
144
145
146class ConnectionClosedOK(ConnectionClosed):
147 """
148 Like :exc:`ConnectionClosed`, when the connection terminated properly.
149
150 A close code with code 1000 (OK) or 1001 (going away) or without a code was
151 received and sent.
152
153 """
154
155
156class ConnectionClosedError(ConnectionClosed):
157 """
158 Like :exc:`ConnectionClosed`, when the connection terminated with an error.
159
160 A close frame with a code other than 1000 (OK) or 1001 (going away) was
161 received or sent, or the closing handshake didn't complete properly.
162
163 """
164
165
166class InvalidURI(WebSocketException):
167 """
168 Raised when connecting to a URI that isn't a valid WebSocket URI.
169
170 """
171
172 def __init__(self, uri: str, msg: str) -> None:
173 self.uri = uri
174 self.msg = msg
175
176 def __str__(self) -> str:
177 return f"{self.uri} isn't a valid URI: {self.msg}"
178
179
180class InvalidProxy(WebSocketException):
181 """
182 Raised when connecting via a proxy that isn't valid.
183
184 """
185
186 def __init__(self, proxy: str, msg: str) -> None:
187 self.proxy = proxy
188 self.msg = msg
189
190 def __str__(self) -> str:
191 return f"{self.proxy} isn't a valid proxy: {self.msg}"
192
193
194class InvalidHandshake(WebSocketException):
195 """
196 Base class for exceptions raised when the opening handshake fails.
197
198 """
199
200
201class SecurityError(InvalidHandshake):
202 """
203 Raised when a handshake request or response breaks a security rule.
204
205 Security limits can be configured with :doc:`environment variables
206 <../reference/variables>`.
207
208 """
209
210
211class ProxyError(InvalidHandshake):
212 """
213 Raised when failing to connect to a proxy.
214
215 """
216
217
218class InvalidProxyMessage(ProxyError):
219 """
220 Raised when an HTTP proxy response is malformed.
221
222 """
223
224
225class InvalidProxyStatus(ProxyError):
226 """
227 Raised when an HTTP proxy rejects the connection.
228
229 """
230
231 def __init__(self, response: http11.Response) -> None:
232 self.response = response
233
234 def __str__(self) -> str:
235 return f"proxy rejected connection: HTTP {self.response.status_code:d}"
236
237
238class InvalidMessage(InvalidHandshake):
239 """
240 Raised when a handshake request or response is malformed.
241
242 """
243
244
245class InvalidStatus(InvalidHandshake):
246 """
247 Raised when a handshake response rejects the WebSocket upgrade.
248
249 """
250
251 def __init__(self, response: http11.Response) -> None:
252 self.response = response
253
254 def __str__(self) -> str:
255 return (
256 f"server rejected WebSocket connection: HTTP {self.response.status_code:d}"
257 )
258
259
260class InvalidHeader(InvalidHandshake):
261 """
262 Raised when an HTTP header doesn't have a valid format or value.
263
264 """
265
266 def __init__(self, name: str, value: str | None = None) -> None:
267 self.name = name
268 self.value = value
269
270 def __str__(self) -> str:
271 if self.value is None:
272 return f"missing {self.name} header"
273 elif self.value == "":
274 return f"empty {self.name} header"
275 else:
276 return f"invalid {self.name} header: {self.value}"
277
278
279class InvalidHeaderFormat(InvalidHeader):
280 """
281 Raised when an HTTP header cannot be parsed.
282
283 The format of the header doesn't match the grammar for that header.
284
285 """
286
287 def __init__(self, name: str, error: str, header: str, pos: int) -> None:
288 super().__init__(name, f"{error} at {pos} in {header}")
289
290
291class InvalidHeaderValue(InvalidHeader):
292 """
293 Raised when an HTTP header has a wrong value.
294
295 The format of the header is correct but the value isn't acceptable.
296
297 """
298
299
300class InvalidOrigin(InvalidHeader):
301 """
302 Raised when the Origin header in a request isn't allowed.
303
304 """
305
306 def __init__(self, origin: str | None) -> None:
307 super().__init__("Origin", origin)
308
309
310class InvalidUpgrade(InvalidHeader):
311 """
312 Raised when the Upgrade or Connection header isn't correct.
313
314 """
315
316
317class NegotiationError(InvalidHandshake):
318 """
319 Raised when negotiating an extension or a subprotocol fails.
320
321 """
322
323
324class DuplicateParameter(NegotiationError):
325 """
326 Raised when a parameter name is repeated in an extension header.
327
328 """
329
330 def __init__(self, name: str) -> None:
331 self.name = name
332
333 def __str__(self) -> str:
334 return f"duplicate parameter: {self.name}"
335
336
337class InvalidParameterName(NegotiationError):
338 """
339 Raised when a parameter name in an extension header is invalid.
340
341 """
342
343 def __init__(self, name: str) -> None:
344 self.name = name
345
346 def __str__(self) -> str:
347 return f"invalid parameter name: {self.name}"
348
349
350class InvalidParameterValue(NegotiationError):
351 """
352 Raised when a parameter value in an extension header is invalid.
353
354 """
355
356 def __init__(self, name: str, value: str | None) -> None:
357 self.name = name
358 self.value = value
359
360 def __str__(self) -> str:
361 if self.value is None:
362 return f"missing value for parameter {self.name}"
363 elif self.value == "":
364 return f"empty value for parameter {self.name}"
365 else:
366 return f"invalid value for parameter {self.name}: {self.value}"
367
368
369class ProtocolError(WebSocketException):
370 """
371 Raised when receiving or sending a frame that breaks the protocol.
372
373 The Sans-I/O implementation raises this exception when:
374
375 * receiving or sending a frame that contains invalid data;
376 * receiving or sending an invalid sequence of frames.
377
378 """
379
380
381class PayloadTooBig(WebSocketException):
382 """
383 Raised when parsing a frame with a payload that exceeds the maximum size.
384
385 The Sans-I/O layer uses this exception internally. It doesn't bubble up to
386 the I/O layer.
387
388 The :meth:`~websockets.extensions.Extension.decode` method of extensions
389 must raise :exc:`PayloadTooBig` if decoding a frame would exceed the limit.
390
391 """
392
393 def __init__(
394 self,
395 size_or_message: int | None | str,
396 max_size: int | None = None,
397 current_size: int | None = None,
398 ) -> None:
399 if isinstance(size_or_message, str):
400 assert max_size is None
401 assert current_size is None
402 warnings.warn( # deprecated in 14.0 - 2024-11-09
403 "PayloadTooBig(message) is deprecated; "
404 "change to PayloadTooBig(size, max_size)",
405 DeprecationWarning,
406 )
407 self.message: str | None = size_or_message
408 else:
409 self.message = None
410 self.size: int | None = size_or_message
411 assert max_size is not None
412 self.max_size: int = max_size
413 self.current_size: int | None = None
414 self.set_current_size(current_size)
415
416 def __str__(self) -> str:
417 if self.message is not None:
418 return self.message
419 else:
420 message = "frame "
421 if self.size is not None:
422 message += f"with {self.size} bytes "
423 if self.current_size is not None:
424 message += f"after reading {self.current_size} bytes "
425 message += f"exceeds limit of {self.max_size} bytes"
426 return message
427
428 def set_current_size(self, current_size: int | None) -> None:
429 assert self.current_size is None
430 if current_size is not None:
431 self.max_size += current_size
432 self.current_size = current_size
433
434
435class InvalidState(WebSocketException, AssertionError):
436 """
437 Raised when sending a frame is forbidden in the current state.
438
439 Specifically, the Sans-I/O layer raises this exception when:
440
441 * sending a data frame to a connection in a state other
442 :attr:`~websockets.protocol.State.OPEN`;
443 * sending a control frame to a connection in a state other than
444 :attr:`~websockets.protocol.State.OPEN` or
445 :attr:`~websockets.protocol.State.CLOSING`.
446
447 """
448
449
450class ConcurrencyError(WebSocketException, RuntimeError):
451 """
452 Raised when receiving or sending messages concurrently.
453
454 WebSocket is a connection-oriented protocol. Reads must be serialized; so
455 must be writes. However, reading and writing concurrently is possible.
456
457 """
458
459
460# At the bottom to break import cycles created by type annotations.
461from . import frames, http11 # noqa: E402
462
463
464lazy_import(
465 globals(),
466 deprecated_aliases={
467 # deprecated in 14.0 - 2024-11-09
468 "AbortHandshake": ".legacy.exceptions",
469 "InvalidStatusCode": ".legacy.exceptions",
470 "RedirectHandshake": ".legacy.exceptions",
471 "WebSocketProtocolError": ".legacy.exceptions",
472 },
473)