Skip to content

Commit 59d4dcf

Browse files
committed
Reintroduce InvalidMessage.
This improves compatibility with the legacy implementation and clarifies error reporting. Fix #1548.
1 parent d8891a1 commit 59d4dcf

File tree

18 files changed

+136
-40
lines changed

18 files changed

+136
-40
lines changed

docs/project/changelog.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ notice.
3232

3333
*In development*
3434

35+
Bug fixes
36+
.........
37+
38+
* Wrapped errors when reading the opening handshake request or response in
39+
:exc:`~exceptions.InvalidMessage` so that :func:`~asyncio.client.connect`
40+
raises :exc:`~exceptions.InvalidHandshake` or a subclass when the opening
41+
handshake fails.
42+
3543
.. _14.1:
3644

3745
14.1

docs/reference/exceptions.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ also reported by :func:`~websockets.asyncio.server.serve` in logs.
3030

3131
.. autoexception:: InvalidHandshake
3232

33+
.. autoexception:: InvalidMessage
34+
3335
.. autoexception:: SecurityError
3436

3537
.. autoexception:: InvalidStatus
@@ -74,8 +76,6 @@ Legacy exceptions
7476

7577
These exceptions are only used by the legacy :mod:`asyncio` implementation.
7678

77-
.. autoexception:: InvalidMessage
78-
7979
.. autoexception:: InvalidStatusCode
8080

8181
.. autoexception:: AbortHandshake

src/websockets/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"InvalidHeader",
3232
"InvalidHeaderFormat",
3333
"InvalidHeaderValue",
34+
"InvalidMessage",
3435
"InvalidOrigin",
3536
"InvalidParameterName",
3637
"InvalidParameterValue",
@@ -71,6 +72,7 @@
7172
InvalidHeader,
7273
InvalidHeaderFormat,
7374
InvalidHeaderValue,
75+
InvalidMessage,
7476
InvalidOrigin,
7577
InvalidParameterName,
7678
InvalidParameterValue,
@@ -122,6 +124,7 @@
122124
"InvalidHeader": ".exceptions",
123125
"InvalidHeaderFormat": ".exceptions",
124126
"InvalidHeaderValue": ".exceptions",
127+
"InvalidMessage": ".exceptions",
125128
"InvalidOrigin": ".exceptions",
126129
"InvalidParameterName": ".exceptions",
127130
"InvalidParameterValue": ".exceptions",
@@ -159,7 +162,6 @@
159162
"WebSocketClientProtocol": ".legacy.client",
160163
# .legacy.exceptions
161164
"AbortHandshake": ".legacy.exceptions",
162-
"InvalidMessage": ".legacy.exceptions",
163165
"InvalidStatusCode": ".legacy.exceptions",
164166
"RedirectHandshake": ".legacy.exceptions",
165167
"WebSocketProtocolError": ".legacy.exceptions",

src/websockets/asyncio/client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from ..client import ClientProtocol, backoff
1313
from ..datastructures import HeadersLike
14-
from ..exceptions import InvalidStatus, SecurityError
14+
from ..exceptions import InvalidMessage, InvalidStatus, SecurityError
1515
from ..extensions.base import ClientExtensionFactory
1616
from ..extensions.permessage_deflate import enable_client_permessage_deflate
1717
from ..headers import validate_subprotocols
@@ -147,7 +147,9 @@ def process_exception(exc: Exception) -> Exception | None:
147147
That exception will be raised, breaking out of the retry loop.
148148
149149
"""
150-
if isinstance(exc, (EOFError, OSError, asyncio.TimeoutError)):
150+
if isinstance(exc, (OSError, asyncio.TimeoutError)):
151+
return None
152+
if isinstance(exc, InvalidMessage) and isinstance(exc.__cause__, EOFError):
151153
return None
152154
if isinstance(exc, InvalidStatus) and exc.response.status_code in [
153155
500, # Internal Server Error

src/websockets/client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
InvalidHandshake,
1212
InvalidHeader,
1313
InvalidHeaderValue,
14+
InvalidMessage,
1415
InvalidStatus,
1516
InvalidUpgrade,
1617
NegotiationError,
@@ -318,7 +319,10 @@ def parse(self) -> Generator[None]:
318319
self.reader.read_to_eof,
319320
)
320321
except Exception as exc:
321-
self.handshake_exc = exc
322+
self.handshake_exc = InvalidMessage(
323+
"did not receive a valid HTTP response"
324+
)
325+
self.handshake_exc.__cause__ = exc
322326
self.send_eof()
323327
self.parser = self.discard()
324328
next(self.parser) # start coroutine

src/websockets/exceptions.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* :exc:`InvalidURI`
99
* :exc:`InvalidHandshake`
1010
* :exc:`SecurityError`
11-
* :exc:`InvalidMessage` (legacy)
11+
* :exc:`InvalidMessage`
1212
* :exc:`InvalidStatus`
1313
* :exc:`InvalidStatusCode` (legacy)
1414
* :exc:`InvalidHeader`
@@ -48,6 +48,7 @@
4848
"InvalidHeader",
4949
"InvalidHeaderFormat",
5050
"InvalidHeaderValue",
51+
"InvalidMessage",
5152
"InvalidOrigin",
5253
"InvalidUpgrade",
5354
"NegotiationError",
@@ -185,6 +186,13 @@ class SecurityError(InvalidHandshake):
185186
"""
186187

187188

189+
class InvalidMessage(InvalidHandshake):
190+
"""
191+
Raised when a handshake request or response is malformed.
192+
193+
"""
194+
195+
188196
class InvalidStatus(InvalidHandshake):
189197
"""
190198
Raised when a handshake response rejects the WebSocket upgrade.
@@ -410,7 +418,6 @@ class ConcurrencyError(WebSocketException, RuntimeError):
410418
deprecated_aliases={
411419
# deprecated in 14.0 - 2024-11-09
412420
"AbortHandshake": ".legacy.exceptions",
413-
"InvalidMessage": ".legacy.exceptions",
414421
"InvalidStatusCode": ".legacy.exceptions",
415422
"RedirectHandshake": ".legacy.exceptions",
416423
"WebSocketProtocolError": ".legacy.exceptions",

src/websockets/legacy/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from ..exceptions import (
1818
InvalidHeader,
1919
InvalidHeaderValue,
20+
InvalidMessage,
2021
NegotiationError,
2122
SecurityError,
2223
)
@@ -34,7 +35,7 @@
3435
from ..http11 import USER_AGENT
3536
from ..typing import ExtensionHeader, LoggerLike, Origin, Subprotocol
3637
from ..uri import WebSocketURI, parse_uri
37-
from .exceptions import InvalidMessage, InvalidStatusCode, RedirectHandshake
38+
from .exceptions import InvalidStatusCode, RedirectHandshake
3839
from .handshake import build_request, check_response
3940
from .http import read_response
4041
from .protocol import WebSocketCommonProtocol

src/websockets/legacy/exceptions.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,13 @@
33
from .. import datastructures
44
from ..exceptions import (
55
InvalidHandshake,
6+
# InvalidMessage was incorrectly moved here in versions 14.0 and 14.1.
7+
InvalidMessage, # noqa: F401
68
ProtocolError as WebSocketProtocolError, # noqa: F401
79
)
810
from ..typing import StatusLike
911

1012

11-
class InvalidMessage(InvalidHandshake):
12-
"""
13-
Raised when a handshake request or response is malformed.
14-
15-
"""
16-
17-
1813
class InvalidStatusCode(InvalidHandshake):
1914
"""
2015
Raised when a handshake response status code is invalid.

src/websockets/legacy/server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from ..exceptions import (
1818
InvalidHandshake,
1919
InvalidHeader,
20+
InvalidMessage,
2021
InvalidOrigin,
2122
InvalidUpgrade,
2223
NegotiationError,
@@ -32,7 +33,7 @@
3233
from ..http11 import SERVER
3334
from ..protocol import State
3435
from ..typing import ExtensionHeader, LoggerLike, Origin, StatusLike, Subprotocol
35-
from .exceptions import AbortHandshake, InvalidMessage
36+
from .exceptions import AbortHandshake
3637
from .handshake import build_response, check_request
3738
from .http import read_request
3839
from .protocol import WebSocketCommonProtocol, broadcast

src/websockets/server.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
InvalidHandshake,
1414
InvalidHeader,
1515
InvalidHeaderValue,
16+
InvalidMessage,
1617
InvalidOrigin,
1718
InvalidUpgrade,
1819
NegotiationError,
@@ -552,7 +553,10 @@ def parse(self) -> Generator[None]:
552553
self.reader.read_line,
553554
)
554555
except Exception as exc:
555-
self.handshake_exc = exc
556+
self.handshake_exc = InvalidMessage(
557+
"did not receive a valid HTTP request"
558+
)
559+
self.handshake_exc.__cause__ = exc
556560
self.send_eof()
557561
self.parser = self.discard()
558562
next(self.parser) # start coroutine

0 commit comments

Comments
 (0)