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