aio-libs/aiohttp · error · WSMessageTypeError

Received message : is not WSMsgType.TEXT

Error message

Received message {msg.type}:{msg.data!r} is not WSMsgType.TEXT

What it means

Raised by WebSocketResponse.receive_str() as a WSMessageTypeError (a TypeError subclass) when the message returned by receive() is not of type WSMsgType.TEXT. receive_str() expects a text frame; receiving a BINARY, PING, PONG, CLOSE, CLOSING, CLOSED, or ERROR frame trips the guard. The %r formatting includes the actual frame type and payload for diagnosis.

Solutions

  1. Use receive() and switch on msg.type so you can handle TEXT, BINARY, and CLOSE/ERROR separately.
  2. Catch WSMessageTypeError (importable from aiohttp) and treat non-text frames as a protocol error or close.
  3. Align client and server on frame type — if the client sends binary, switch to receive_bytes()/receive_json_bytes().

Example fix

// before
msg = await ws.receive_str()  # raises on binary

// after
from aiohttp import WSMessageTypeError
try:
    text = await ws.receive_str()
except WSMessageTypeError:
    msg = await ws.receive()
    if msg.type == WSMsgType.BINARY:
        text = msg.data.decode('utf-8')
    else:
        raise
# or switch on type up front:
msg = await ws.receive()
if msg.type == WSMsgType.TEXT: ...
elif msg.type == WSMsgType.BINARY: ...
Defensive patterns

Strategy: try-catch

Validate before calling

from aiohttp import WSMessageTypeError
try:
    text = await ws.receive_str()
except WSMessageTypeError:
    msg = await ws.receive()
    if msg.type == WSMsgType.TEXT:
        text = msg.data
    else:
        raise

Type guard

def is_text_msg(msg) -> bool:
    return msg.type is WSMsgType.TEXT

Try / catch

try:
    text = await ws.receive_str()
except WSMessageTypeError as e:
    # peer sent non-text; decide whether to close or switch to binary handling
    await ws.close(code=WSCloseCode.UNSUPPORTED_DATA, message=b'expected text')
    raise

Prevention

When it happens

Trigger: The peer sends a BINARY frame when the server calls receive_str(); the peer sends CLOSE/CLOSED mid-stream; receive_str() is used in a loop and a control frame arrives.

Common situations: Client and server disagree on text-vs-binary framing (e.g. client sends raw bytes for JSON); a disconnect causes a CLOSE frame that receive_str does not anticipate; using receive_str when the protocol actually transmits binary.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/4d1621b6fee91575. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/web_ws.py:693

    @overload
    async def receive_str(
        self: "WebSocketResponse[Literal[False]]", *, timeout: float | None = None
    ) -> bytes: ...

    @overload
    async def receive_str(
        self: "WebSocketResponse[_DecodeText]", *, timeout: float | None = None
    ) -> str | bytes: ...

    async def receive_str(self, *, timeout: float | None = None) -> str | bytes:
        """Receive TEXT message.

        Returns str when decode_text=True (default), bytes when decode_text=False.
        """
        msg = await self.receive(timeout)
        if msg.type is not WSMsgType.TEXT:
            raise WSMessageTypeError(
                f"Received message {msg.type}:{msg.data!r} is not WSMsgType.TEXT"
            )
        return msg.data

    async def receive_bytes(self, *, timeout: float | None = None) -> bytes:
        msg = await self.receive(timeout)
        if msg.type is not WSMsgType.BINARY:
            raise WSMessageTypeError(
                f"Received message {msg.type}:{msg.data!r} is not WSMsgType.BINARY"
            )
        return msg.data

    @overload
    async def receive_json(
        self: "WebSocketResponse[Literal[True]]",
        *,
        loads: JSONDecoder = ...,
        timeout: float | None = None,

View on GitHub (pinned to d041d4d0fd)