<img src="/img/tutorial/websockets/image05.png">
+## Handling disconnections and multiple clients
+
+When a WebSocket connection is closed, the `await websocket.receive_text()` will raise a `WebSocketDisconnect` exception, which you can then catch and handle like in this example.
+
+```Python hl_lines="81-83"
+{!../../../docs_src/websockets/tutorial003.py!}
+```
+
+To try it out:
+
+* Open the app with several browser tabs.
+* Write messages from them.
+* Then close one of the tabs.
+
+That will raise the `WebSocketDisconnect` exception, and all the other clients will receive a message like:
+
+```
+Client #1596980209979 left the chat
+```
+
+!!! tip
+ The app above is a minimal and simple example to demonstrate how to handle and broadcast messages to several WebSocket connections.
+
+ But have in mind that, as everything is handled in memory, in a single list, it will only work while the process is running, and will only work with a single process.
+
+ If you need something easy to integrate with FastAPI but that is more robust, supported by Redis, PostgreSQL or others, check <a href="https://github.com/encode/broadcaster" class="external-link" target="_blank">encode/broadcaster</a>.
+
## More info
To learn more about the options, check Starlette's documentation for:
--- /dev/null
+from typing import List
+
+from fastapi import FastAPI, WebSocket, WebSocketDisconnect
+from fastapi.responses import HTMLResponse
+
+app = FastAPI()
+
+html = """
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Chat</title>
+ </head>
+ <body>
+ <h1>WebSocket Chat</h1>
+ <h2>Your ID: <span id="ws-id"></span></h2>
+ <form action="" onsubmit="sendMessage(event)">
+ <input type="text" id="messageText" autocomplete="off"/>
+ <button>Send</button>
+ </form>
+ <ul id='messages'>
+ </ul>
+ <script>
+ var client_id = Date.now()
+ document.querySelector("#ws-id").textContent = client_id;
+ var ws = new WebSocket(`ws://localhost:8000/ws/${client_id}`);
+ ws.onmessage = function(event) {
+ var messages = document.getElementById('messages')
+ var message = document.createElement('li')
+ var content = document.createTextNode(event.data)
+ message.appendChild(content)
+ messages.appendChild(message)
+ };
+ function sendMessage(event) {
+ var input = document.getElementById("messageText")
+ ws.send(input.value)
+ input.value = ''
+ event.preventDefault()
+ }
+ </script>
+ </body>
+</html>
+"""
+
+
+class ConnectionManager:
+ def __init__(self):
+ self.active_connections: List[WebSocket] = []
+
+ async def connect(self, websocket: WebSocket):
+ await websocket.accept()
+ self.active_connections.append(websocket)
+
+ def disconnect(self, websocket: WebSocket):
+ self.active_connections.remove(websocket)
+
+ async def send_personal_message(self, message: str, websocket: WebSocket):
+ await websocket.send_text(message)
+
+ async def broadcast(self, message: str):
+ for connection in self.active_connections:
+ await connection.send_text(message)
+
+
+manager = ConnectionManager()
+
+
+@app.get("/")
+async def get():
+ return HTMLResponse(html)
+
+
+@app.websocket("/ws/{client_id}")
+async def websocket_endpoint(websocket: WebSocket, client_id: int):
+ await manager.connect(websocket)
+ try:
+ while True:
+ data = await websocket.receive_text()
+ await manager.send_personal_message(f"You wrote: {data}", websocket)
+ await manager.broadcast(f"Client #{client_id} says: {data}")
+ except WebSocketDisconnect:
+ manager.disconnect(websocket)
+ await manager.broadcast(f"Client #{client_id} left the chat")
--- /dev/null
+from fastapi.testclient import TestClient
+
+from docs_src.websockets.tutorial003 import app
+
+client = TestClient(app)
+
+
+def test_websocket_handle_disconnection():
+ with client.websocket_connect("/ws/1234") as connection, client.websocket_connect(
+ "/ws/5678"
+ ) as connection_two:
+ connection.send_text("Hello from 1234")
+ data1 = connection.receive_text()
+ assert data1 == "You wrote: Hello from 1234"
+ data2 = connection_two.receive_text()
+ client1_says = "Client #1234 says: Hello from 1234"
+ assert data2 == client1_says
+ data1 = connection.receive_text()
+ assert data1 == client1_says
+ connection_two.close()
+ data1 = connection.receive_text()
+ assert data1 == "Client #5678 left the chat"