Skip to content

Chapter 17 · WebSocket Real-Time Communication ​

Chapter goals: Build bidirectional long connections with @app.websocket, implement send/receive loops, connection manager broadcast and disconnect handling, and understand dependency usage and limitations in WebSocket endpoints.

17.1 HTTP Request vs WebSocket Connection ​

HTTP is ask-and-answer: the server never gets a chance to speak unless the client sends a request. Chat rooms, market data feeds, collaborative editing — scenarios where "the server must push proactively" need WebSocket: after one handshake upgrade, both sides can send messages at any time over the same TCP long connection.

FastAPI (Starlette) has native WebSocket support. Install the official example client library first (the server side needs no extra dependency):

bash
uv add websockets

17.2 Minimal Echo Server ​

python
from fastapi import FastAPI, WebSocket

app = FastAPI()


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()          # Complete the handshake; connection officially established
    while True:                       # A single connection can loop through任意 number of messages
        data = await websocket.receive_text()
        await websocket.send_text(f"Message text was: {data}")
    # When the loop exits (client disconnects and raises an exception), the connection ends

In addition to text, there are three symmetric method pairs: send_json/receive_json (auto-serialize/deserialize), send_bytes/receive_bytes (binary).

17.3 Browser-Side Integration ​

Build a complete app with a built-in test page:

python
from fastapi import FastAPI, WebSocket
from fastapi.responses import HTMLResponse

app = FastAPI()

html = """
<!DOCTYPE html>
<html>
<body>
  <input id="msg" placeholder="Type a message"/>
  <button onclick="send()">Send</button>
  <ul id="log"></ul>
  <script>
    const ws = new WebSocket("ws://127.0.0.1:8000/ws");
    ws.onmessage = (event) => {           // Receive server push
      document.getElementById("log")
        .insertAdjacentHTML("beforeend", `<li>\${event.data}</li>`);
    };
    function send() {
      ws.send(document.getElementById("msg").value);   // Send to server
    }
  </script>
</body>
</html>
"""


@app.get("/")
async def page():
    return HTMLResponse(html)


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    while True:
        data = await websocket.receive_text()
        await websocket.send_text(f"Echo: {data}")

Note the frontend URL uses ws:// protocol (wss:// under HTTPS). Open multiple browser tabs — each is an独立 connection, sending and receiving independently.

17.4 Disconnect Handling and Multi-Client Broadcast ​

When the client closes the page, the blocking await websocket.receive_text() raises a WebSocketDisconnect exception — this is your signal to clean up. Paired with a "connection manager" class, you can implement chat-room broadcast:

python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()


class ConnectionManager:
    def __init__(self):
        self.active: list[WebSocket] = []

    async def connect(self, ws: WebSocket):
        await ws.accept()
        self.active.append(ws)

    def disconnect(self, ws: WebSocket):
        self.active.remove(ws)

    async def broadcast(self, message: str):
        for ws in self.active:
            await ws.send_text(message)


manager = ConnectionManager()


@app.websocket("/ws/chat")
async def chat_websocket(websocket: WebSocket):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await manager.broadcast(f"New message: {data}")
    except WebSocketDisconnect:
        manager.disconnect(websocket)

17.5 Dependencies in WebSocket Endpoints ​

WebSocket endpoints support dependencies, but with restrictions:

  • Depends works normally for auth, DB sessions, etc.;
  • BackgroundTasks does NOT work in WebSocket endpoints (no response to defer after);
  • Yield dependencies work for setup/teardown around the connection lifetime.
python
@app.websocket("/ws/secure")
async def secure_ws(
    websocket: WebSocket,
    current_user: Annotated[User, Depends(get_current_user)],
):
    await websocket.accept()
    # current_user is available; use it to scope the connection
    ...

17.6 Chapter Summary ​

  • WebSocket: bidirectional long connection; await websocket.accept() then loop receive_text/send_text;
  • Connection manager pattern: maintain an active list; broadcast on disconnect;
  • WebSocketDisconnect exception signals client closure;
  • Dependencies work in WebSocket endpoints; BackgroundTasks do not.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. What does websocket.accept() do?

2. What exception is raised when a WebSocket client disconnects?

3. Can you use BackgroundTasks in a WebSocket endpoint?

4. What protocol prefix does the browser WebSocket use for non-HTTPS pages?

🛠️ Hands-On Exercises ​

  1. Build an echo WebSocket server; test with the browser demo page and verify bidirectional communication.
  2. Implement the ConnectionManager broadcast pattern; open two browser tabs and verify messages sent from one appear in the other.
  3. Add a get_current_user dependency to the WebSocket endpoint; verify unauthenticated connections are rejected with 403.

Proceed to Chapter 18: Template Rendering and Static Files.