mirror of
https://github.com/kennethreitz/responder.git
synced 2026-06-05 14:50:19 +00:00
a3b49ab9fd
New documentation pages: - Authentication: API keys, JWT tokens, session auth, custom exceptions - WebSocket Tutorial: echo server, chat room, HTML client, data formats - Writing Middleware: hooks vs middleware, Starlette integration, ordering - Configuration: env vars, .env files, secret keys, debug mode, production setup Also: - Complete working example at end of quickstart with cross-references - Three new example files: rest_api.py, websocket_chat.py, sse_stream.py - Changelog updated for v3.2.0 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
172 lines
4.7 KiB
ReStructuredText
172 lines
4.7 KiB
ReStructuredText
WebSocket Tutorial
|
|
==================
|
|
|
|
HTTP is request-response — the client asks, the server answers, and the
|
|
connection closes. WebSockets upgrade that into a persistent, bidirectional
|
|
channel where both sides can send messages at any time. This is what powers
|
|
chat apps, live dashboards, multiplayer games, and collaborative editors.
|
|
|
|
This tutorial builds a simple chat room to show how WebSockets work in
|
|
Responder.
|
|
|
|
|
|
How WebSockets Work
|
|
-------------------
|
|
|
|
1. The client sends a normal HTTP request with an ``Upgrade: websocket``
|
|
header.
|
|
2. The server accepts the upgrade and the connection switches protocols.
|
|
3. Both sides can now send messages freely — no more request/response.
|
|
4. Either side can close the connection at any time.
|
|
|
|
In Responder, WebSocket routes receive a ``ws`` object instead of
|
|
``req`` and ``resp``. The ``ws`` object has methods for accepting the
|
|
connection, sending and receiving data, and closing.
|
|
|
|
|
|
Echo Server
|
|
-----------
|
|
|
|
The simplest WebSocket — echoes everything back::
|
|
|
|
@api.route("/ws", websocket=True)
|
|
async def echo(ws):
|
|
await ws.accept()
|
|
while True:
|
|
data = await ws.receive_text()
|
|
await ws.send_text(f"Echo: {data}")
|
|
|
|
The ``await ws.accept()`` call completes the WebSocket handshake. After
|
|
that, you're in a loop — receive a message, send a response.
|
|
|
|
Test it with a WebSocket client::
|
|
|
|
$ pip install websocket-client
|
|
$ python -c "
|
|
import websocket
|
|
ws = websocket.create_connection('ws://localhost:5042/ws')
|
|
ws.send('hello')
|
|
print(ws.recv()) # Echo: hello
|
|
ws.close()
|
|
"
|
|
|
|
|
|
Chat Room
|
|
---------
|
|
|
|
A chat room needs to broadcast messages to all connected clients. We keep
|
|
a set of active connections and iterate through them when someone sends
|
|
a message::
|
|
|
|
connected = set()
|
|
|
|
@api.route("/chat", websocket=True)
|
|
async def chat(ws):
|
|
await ws.accept()
|
|
connected.add(ws)
|
|
try:
|
|
while True:
|
|
message = await ws.receive_text()
|
|
# Broadcast to all connected clients
|
|
for client in connected:
|
|
await client.send_text(message)
|
|
except Exception:
|
|
pass
|
|
finally:
|
|
connected.discard(ws)
|
|
|
|
The ``try/finally`` block ensures we remove disconnected clients from
|
|
the set, even if the connection drops unexpectedly.
|
|
|
|
|
|
Data Formats
|
|
------------
|
|
|
|
WebSockets support three data formats:
|
|
|
|
**Text** — plain strings::
|
|
|
|
await ws.send_text("hello")
|
|
message = await ws.receive_text()
|
|
|
|
**JSON** — auto-serialized Python objects::
|
|
|
|
await ws.send_json({"type": "update", "data": [1, 2, 3]})
|
|
message = await ws.receive_json()
|
|
|
|
**Binary** — raw bytes, useful for images, audio, or custom protocols::
|
|
|
|
await ws.send_bytes(b"\x00\x01\x02")
|
|
data = await ws.receive_bytes()
|
|
|
|
|
|
HTML Client
|
|
-----------
|
|
|
|
Here's a minimal HTML page that connects to the chat room. The browser's
|
|
built-in ``WebSocket`` API handles everything — no libraries needed:
|
|
|
|
.. code-block:: html
|
|
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<body>
|
|
<div id="messages"></div>
|
|
<input id="input" placeholder="Type a message..." />
|
|
<script>
|
|
const ws = new WebSocket("ws://localhost:5042/chat");
|
|
const messages = document.getElementById("messages");
|
|
const input = document.getElementById("input");
|
|
|
|
ws.onmessage = (event) => {
|
|
const p = document.createElement("p");
|
|
p.textContent = event.data;
|
|
messages.appendChild(p);
|
|
};
|
|
|
|
input.addEventListener("keypress", (e) => {
|
|
if (e.key === "Enter") {
|
|
ws.send(input.value);
|
|
input.value = "";
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|
|
Save this as ``static/index.html`` and serve it with Responder's
|
|
built-in static file support.
|
|
|
|
|
|
Before-Request Hooks for WebSockets
|
|
------------------------------------
|
|
|
|
You can run code before a WebSocket connection is established, just like
|
|
HTTP before-request hooks. This is useful for authentication::
|
|
|
|
@api.before_request(websocket=True)
|
|
async def ws_auth(ws):
|
|
# Check for a token in the query string
|
|
# (WebSocket headers are limited in browsers)
|
|
await ws.accept()
|
|
|
|
WebSocket before-request hooks receive the ``ws`` object and must call
|
|
``await ws.accept()`` if they want the connection to proceed.
|
|
|
|
|
|
Testing WebSockets
|
|
------------------
|
|
|
|
Use Starlette's ``TestClient`` for WebSocket tests::
|
|
|
|
from starlette.testclient import TestClient
|
|
|
|
def test_echo():
|
|
client = TestClient(api)
|
|
with client.websocket_connect("/ws") as ws:
|
|
ws.send_text("hello")
|
|
assert ws.receive_text() == "Echo: hello"
|
|
|
|
The ``websocket_connect`` context manager handles the connection
|
|
lifecycle — it connects on enter and disconnects on exit.
|