NEW: Fenrir v4.1.2 — 56 Bug Fixes, Performance Optimizations, 1331 Tests, 5-Framework Benchmark! Read the changelog
websocket.md
docs websocket.md

WebSocket

The WebSocket class provides a full-duplex communication channel over a single TCP connection, conforming to the WebSocket protocol. It supports text, binary, and JSON messages, configurable timeouts, and state tracking.

Exceptions

WebSocketDisconnect

Raised when a client disconnects. Inherits from Exception.

Attribute Type Default Description
code int 1000 WebSocket close status code
reason str "" Human-readable close reason

WebSocketTimeout

Raised when a receive operation exceeds the configured timeout. Inherits from Exception.

Attribute Type Description
timeout float The timeout value in seconds that was exceeded

WebSocket Class

Constructor

1
WebSocket(scope, receive, send, timeout=None)
Parameter Type Default Description
scope Dict[str, Any] ASGI scope dictionary (populated by the framework)
receive Any ASGI receive callable (populated by the framework)
send Any ASGI send callable (populated by the framework)
timeout Optional[float] None Per-connection timeout in seconds for receive operations

When timeout is set, every call to receive(), receive_text(), receive_bytes(), and receive_json() will raise WebSocketTimeout if no message arrives within the specified duration.

Attributes

client_state

Tracks the current state of the WebSocket connection.

Value Description
"CONNECTING" Initial state before accept() is called
"CONNECTED" After a successful accept() call
"DISCONNECTED" After the client disconnects or close() is called
1
2
3
print(websocket.client_state)  # "CONNECTING"
await websocket.accept()
print(websocket.client_state)  # "CONNECTED"

Methods

accept(subprotocol=None, headers=None)

Completes the WebSocket handshake and transitions the connection from CONNECTING to CONNECTED. Must be called before any other communication.

Parameter Type Default Description
subprotocol str or None None Subprotocol to negotiate with the client
headers list or None None Additional headers to include in the accept frame

Raises RuntimeError if the WebSocket is not in the CONNECTING state.

1
2
3
4
5
6
7
await websocket.accept()

# With subprotocol
await websocket.accept(subprotocol="graphql-ws")

# With headers
await websocket.accept(headers=[["X-Custom", "value"]])

receive() -> Dict[str, Any]

Waits for and returns the next raw message as a dictionary. This is the base receive method that all typed receive methods delegate to. Respects the connection-level timeout if set.

1
2
3
4
5
message = await websocket.receive()
# {"type": "websocket.receive", "text": "hello"}

message = await websocket.receive()
# {"type": "websocket.receive", "bytes": b"\x00\x01"}

Raises WebSocketDisconnect when the client disconnects. Raises WebSocketTimeout if a timeout is configured and no message arrives in time.

receive_text() -> str

Waits for and returns the next text message. Delegates to receive() internally.

1
data = await websocket.receive_text()

Raises ValueError if the message does not contain a text field.

receive_bytes() -> bytes

Waits for and returns the next binary message. Delegates to receive() internally.

1
data = await websocket.receive_bytes()

Raises ValueError if the message does not contain a bytes field.

receive_json() -> Any

Waits for and returns the next message parsed as JSON. Delegates to receive_text() internally.

1
data = await websocket.receive_json()

send_text(text: str)

Sends a text message to the client.

1
await websocket.send_text("Hello, world!")

Raises RuntimeError if the WebSocket is not in the CONNECTED state.

send_bytes(data: bytes)

Sends a binary message to the client.

1
await websocket.send_bytes(b"\x00\x01\x02")

Raises RuntimeError if the WebSocket is not in the CONNECTED state.

send_json(data: Any)

Sends a JSON message to the client. Serializes data to JSON text and delegates to send_text().

1
await websocket.send_json({"status": "ok", "count": 42})

close(code=1000, reason="")

Closes the WebSocket connection with an optional status code and reason.

Parameter Type Default Description
code int 1000 WebSocket close status code
reason str "" Human-readable close reason

If the connection is already disconnected, this is a no-op.

1
2
3
4
await websocket.close()

# With custom code and reason
await websocket.close(code=1001, reason="Going away")

Per-Route Timeout

Set a timeout on individual WebSocket routes by passing the timeout parameter to the route decorator. This applies a per-connection timeout to all receive operations on that route.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@app.websocket("/ws/echo", timeout=30.0)
async def websocket_echo(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
            await websocket.send_text(f"Echo: {data}")
    except WebSocketTimeout:
        print("Client timed out")
        await websocket.close(code=1000, reason="Timeout")
    except WebSocketDisconnect:
        print("Client disconnected")
Parameter Type Description
timeout float Seconds to wait for a message before raising WebSocketTimeout

Examples

Basic WebSocket Echo Server

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from fenrir import WebSocket, WebSocketDisconnect

@app.websocket("/ws/echo")
async def websocket_echo(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
            await websocket.send_text(f"Echo: {data}")
    except WebSocketDisconnect:
        print("Client disconnected")

WebSocket with Timeout

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from fenrir import WebSocket, WebSocketDisconnect, WebSocketTimeout

@app.websocket("/ws/echo", timeout=30.0)
async def websocket_echo(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
            await websocket.send_text(f"Echo: {data}")
    except WebSocketTimeout:
        print("Client timed out")
        await websocket.close(code=1000, reason="Timeout")
    except WebSocketDisconnect:
        print("Client disconnected")

WebSocket Chat Application

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from typing import Set

active_connections: Set[WebSocket] = set()

@app.websocket("/ws/chat/<room_id>")
async def websocket_chat(websocket: WebSocket, room_id: str):
    await websocket.accept()
    active_connections.add(websocket)

    try:
        while True:
            data = await websocket.receive_text()
            for connection in active_connections:
                try:
                    await connection.send_text(f"[Room {room_id}] {data}")
                except:
                    pass
    except WebSocketDisconnect:
        active_connections.remove(websocket)

WebSocket with JSON Messages

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@app.websocket("/ws/json")
async def websocket_json(websocket: WebSocket):
    await websocket.accept()

    try:
        while True:
            data = await websocket.receive_json()
            result = {"received": data, "processed": True}
            await websocket.send_json(result)
    except WebSocketDisconnect:
        print("WebSocket disconnected")

WebSocket with Subprotocol Negotiation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@app.websocket("/ws/graphql")
async def websocket_graphql(websocket: WebSocket):
    await websocket.accept(subprotocol="graphql-ws")

    try:
        while True:
            data = await websocket.receive_json()
            # Handle GraphQL subscription messages
            await websocket.send_json({"type": "next", "id": "1", "payload": {}})
    except WebSocketDisconnect:
        print("Client disconnected")

WebSocket with Custom Close Codes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@app.websocket("/ws/secure")
async def websocket_secure(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
            if data == "shutdown":
                await websocket.close(code=1001, reason="Server shutting down")
                return
            await websocket.send_text(f"Received: {data}")
    except WebSocketDisconnect as e:
        print(f"Client disconnected with code {e.code}: {e.reason}")

Tracking Connection State

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@app.websocket("/ws/status")
async def websocket_status(websocket: WebSocket):
    print(f"State: {websocket.client_state}")  # "CONNECTING"
    await websocket.accept()
    print(f"State: {websocket.client_state}")  # "CONNECTED"

    try:
        while True:
            data = await websocket.receive_text()
            await websocket.send_text(f"State: {websocket.client_state}")
    except WebSocketDisconnect:
        print(f"State: {websocket.client_state}")  # "DISCONNECTED"

Raw Message Receiving

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@app.websocket("/ws/raw")
async def websocket_raw(websocket: WebSocket):
    await websocket.accept()

    try:
        while True:
            message = await websocket.receive()
            msg_type = message["type"]

            if "text" in message:
                await websocket.send_text(f"Got text: {message['text']}")
            elif "bytes" in message:
                await websocket.send_bytes(message["bytes"])
    except WebSocketDisconnect:
        print("Client disconnected")
Edit on GitHub Last Updated: Oct 20, 2018
© 2026 Fenrir Project.
main*
v4.1.2
Ln 1, Col 1
UTF-8
Prettier
Light Mode
Markdown