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