mirror of
https://github.com/kennethreitz/responder.git
synced 2026-10-24 05:59:12 +00:00
419 lines
39 KiB
HTML
419 lines
39 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<html lang="en" data-content_root="./">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
|
||
<title>WebSocket Tutorial — responder 9.2.0 documentation</title>
|
||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=5ecbeea2" />
|
||
<link rel="stylesheet" type="text/css" href="_static/basic.css?v=b08954a9" />
|
||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=eb4102c5" />
|
||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||
<link rel="stylesheet" type="text/css" href="_static/sphinx-design.min.css?v=95c83b7e" />
|
||
<link rel="stylesheet" type="text/css" href="_static/design-elements.e5416f61bae5d36adc6d722a2b6f8cff.css?v=452a8e97" />
|
||
<script src="_static/documentation_options.js?v=e8b940be"></script>
|
||
<script src="_static/doctools.js?v=fd6eb6e6"></script>
|
||
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
|
||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||
<script src="_static/copybutton.js?v=fd10adb8"></script>
|
||
<script>
|
||
</script>
|
||
<script src="_static/design-tabs.js?v=f930bc37"></script>
|
||
<script src="_static/design-elements.bbdccc18c4abea9397628f9fea3d48c2.js?v=03c7770e"></script>
|
||
<link rel="index" title="Index" href="genindex.html" />
|
||
<link rel="search" title="Search" href="search.html" />
|
||
<link rel="next" title="Writing Middleware" href="tutorial-middleware.html" />
|
||
<link rel="prev" title="Authentication" href="tutorial-auth.html" />
|
||
|
||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
||
|
||
|
||
|
||
|
||
|
||
</head><body>
|
||
|
||
|
||
<div class="document">
|
||
<div class="documentwrapper">
|
||
<div class="bodywrapper">
|
||
|
||
|
||
<div class="body" role="main">
|
||
|
||
<section id="websocket-tutorial">
|
||
<h1>WebSocket Tutorial<a class="headerlink" href="#websocket-tutorial" title="Link to this heading">¶</a></h1>
|
||
<p>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.</p>
|
||
<p>This tutorial builds a simple chat room to show how WebSockets work in
|
||
Responder.</p>
|
||
<section id="how-websockets-work">
|
||
<h2>How WebSockets Work<a class="headerlink" href="#how-websockets-work" title="Link to this heading">¶</a></h2>
|
||
<ol class="arabic simple">
|
||
<li><p>The client sends a normal HTTP request with an <code class="docutils literal notranslate"><span class="pre">Upgrade:</span> <span class="pre">websocket</span></code>
|
||
header.</p></li>
|
||
<li><p>The server accepts the upgrade and the connection switches protocols.</p></li>
|
||
<li><p>Both sides can now send messages freely — no more request/response.</p></li>
|
||
<li><p>Either side can close the connection at any time.</p></li>
|
||
</ol>
|
||
<p>In Responder, WebSocket routes receive a <code class="docutils literal notranslate"><span class="pre">ws</span></code> object instead of
|
||
<code class="docutils literal notranslate"><span class="pre">req</span></code> and <code class="docutils literal notranslate"><span class="pre">resp</span></code>. The <code class="docutils literal notranslate"><span class="pre">ws</span></code> object has methods for accepting the
|
||
connection, sending and receiving data, and closing.</p>
|
||
</section>
|
||
<section id="echo-server">
|
||
<h2>Echo Server<a class="headerlink" href="#echo-server" title="Link to this heading">¶</a></h2>
|
||
<p>The simplest WebSocket — echoes everything back:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/ws"</span><span class="p">,</span> <span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">echo</span><span class="p">(</span><span class="n">ws</span><span class="p">):</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
|
||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">receive_text</span><span class="p">()</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">send_text</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Echo: </span><span class="si">{</span><span class="n">data</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">await</span> <span class="pre">ws.accept()</span></code> call completes the WebSocket handshake. After
|
||
that, you’re in a loop — receive a message, send a response.</p>
|
||
<p>Test it with a WebSocket client:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ 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()
|
||
"
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="chat-room">
|
||
<h2>Chat Room<a class="headerlink" href="#chat-room" title="Link to this heading">¶</a></h2>
|
||
<p>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:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">starlette.websockets</span><span class="w"> </span><span class="kn">import</span> <span class="n">WebSocketDisconnect</span>
|
||
|
||
<span class="n">connected</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/chat"</span><span class="p">,</span> <span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">chat</span><span class="p">(</span><span class="n">ws</span><span class="p">):</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
|
||
<span class="n">connected</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">ws</span><span class="p">)</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||
<span class="n">message</span> <span class="o">=</span> <span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">receive_text</span><span class="p">()</span>
|
||
<span class="c1"># Broadcast to all connected clients</span>
|
||
<span class="k">for</span> <span class="n">client</span> <span class="ow">in</span> <span class="n">connected</span><span class="p">:</span>
|
||
<span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">send_text</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
|
||
<span class="k">except</span> <span class="n">WebSocketDisconnect</span><span class="p">:</span>
|
||
<span class="k">pass</span>
|
||
<span class="k">finally</span><span class="p">:</span>
|
||
<span class="n">connected</span><span class="o">.</span><span class="n">discard</span><span class="p">(</span><span class="n">ws</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">try/finally</span></code> block ensures we remove disconnected clients from
|
||
the set, even if the connection drops unexpectedly. Catching
|
||
<code class="docutils literal notranslate"><span class="pre">WebSocketDisconnect</span></code> specifically (rather than bare <code class="docutils literal notranslate"><span class="pre">Exception</span></code>)
|
||
makes the intent clear and avoids swallowing real bugs.</p>
|
||
</section>
|
||
<section id="data-formats">
|
||
<h2>Data Formats<a class="headerlink" href="#data-formats" title="Link to this heading">¶</a></h2>
|
||
<p>WebSockets support three data formats:</p>
|
||
<p><strong>Text</strong> — plain strings:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">send_text</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">)</span>
|
||
<span class="n">message</span> <span class="o">=</span> <span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">receive_text</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><strong>JSON</strong> — auto-serialized Python objects:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">send_json</span><span class="p">({</span><span class="s2">"type"</span><span class="p">:</span> <span class="s2">"update"</span><span class="p">,</span> <span class="s2">"data"</span><span class="p">:</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]})</span>
|
||
<span class="n">message</span> <span class="o">=</span> <span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">receive_json</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><strong>Binary</strong> — raw bytes, useful for images, audio, or custom protocols:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">send_bytes</span><span class="p">(</span><span class="sa">b</span><span class="s2">"</span><span class="se">\x00\x01\x02</span><span class="s2">"</span><span class="p">)</span>
|
||
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">receive_bytes</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="html-client">
|
||
<h2>HTML Client<a class="headerlink" href="#html-client" title="Link to this heading">¶</a></h2>
|
||
<p>Here’s a minimal HTML page that connects to the chat room. The browser’s
|
||
built-in <code class="docutils literal notranslate"><span class="pre">WebSocket</span></code> API handles everything — no libraries needed:</p>
|
||
<div class="highlight-html notranslate"><div class="highlight"><pre><span></span><span class="cp"><!DOCTYPE html></span>
|
||
<span class="p"><</span><span class="nt">html</span><span class="p">></span>
|
||
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
|
||
<span class="p"><</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">"messages"</span><span class="p">></</span><span class="nt">div</span><span class="p">></span>
|
||
<span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="o">=</span><span class="s">"input"</span> <span class="na">placeholder</span><span class="o">=</span><span class="s">"Type a message..."</span> <span class="p">/></span>
|
||
<span class="p"><</span><span class="nt">script</span><span class="p">></span>
|
||
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">ws</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">WebSocket</span><span class="p">(</span><span class="s2">"ws://localhost:5042/chat"</span><span class="p">);</span>
|
||
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">messages</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">"messages"</span><span class="p">);</span>
|
||
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">input</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">"input"</span><span class="p">);</span>
|
||
|
||
<span class="w"> </span><span class="nx">ws</span><span class="p">.</span><span class="nx">onmessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="nx">event</span><span class="p">)</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="p">{</span>
|
||
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">p</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">"p"</span><span class="p">);</span>
|
||
<span class="w"> </span><span class="nx">p</span><span class="p">.</span><span class="nx">textContent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">event</span><span class="p">.</span><span class="nx">data</span><span class="p">;</span>
|
||
<span class="w"> </span><span class="nx">messages</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">p</span><span class="p">);</span>
|
||
<span class="w"> </span><span class="p">};</span>
|
||
|
||
<span class="w"> </span><span class="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"keypress"</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="nx">e</span><span class="p">)</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="p">{</span>
|
||
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">key</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="s2">"Enter"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
|
||
<span class="w"> </span><span class="nx">ws</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">input</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
|
||
<span class="w"> </span><span class="nx">input</span><span class="p">.</span><span class="nx">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span><span class="p">;</span>
|
||
<span class="w"> </span><span class="p">}</span>
|
||
<span class="w"> </span><span class="p">});</span>
|
||
<span class="w"> </span><span class="p"></</span><span class="nt">script</span><span class="p">></span>
|
||
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
|
||
<span class="p"></</span><span class="nt">html</span><span class="p">></span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Save this as <code class="docutils literal notranslate"><span class="pre">static/index.html</span></code> and serve it with Responder’s
|
||
built-in static file support.</p>
|
||
</section>
|
||
<section id="before-request-hooks-for-websockets">
|
||
<h2>Before-Request Hooks for WebSockets<a class="headerlink" href="#before-request-hooks-for-websockets" title="Link to this heading">¶</a></h2>
|
||
<p>You can run code before a WebSocket connection is established, just like
|
||
HTTP before-request hooks. This is useful for authentication:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">before_request</span><span class="p">(</span><span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">ws_auth</span><span class="p">(</span><span class="n">ws</span><span class="p">):</span>
|
||
<span class="c1"># Check for a token in the query string</span>
|
||
<span class="c1"># (WebSocket headers are limited in browsers)</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>WebSocket before-request hooks receive the <code class="docutils literal notranslate"><span class="pre">ws</span></code> object and must call
|
||
<code class="docutils literal notranslate"><span class="pre">await</span> <span class="pre">ws.accept()</span></code> if they want the connection to proceed.</p>
|
||
<p>To reject a connection, close it from the hook — the route handler is
|
||
then skipped entirely:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">before_request</span><span class="p">(</span><span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">ws_auth</span><span class="p">(</span><span class="n">ws</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="n">ws</span><span class="o">.</span><span class="n">query_params</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"token"</span><span class="p">)</span> <span class="o">!=</span> <span class="s2">"secret"</span><span class="p">:</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">close</span><span class="p">(</span><span class="n">code</span><span class="o">=</span><span class="mi">4401</span><span class="p">)</span> <span class="c1"># handler never runs</span>
|
||
<span class="k">return</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>WebSocket handlers can also receive path parameters and registered
|
||
dependencies by declaring them after <code class="docutils literal notranslate"><span class="pre">ws</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/chat/</span><span class="si">{room}</span><span class="s2">"</span><span class="p">,</span> <span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">chat</span><span class="p">(</span><span class="n">ws</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">room</span><span class="p">):</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">send_text</span><span class="p">(</span><span class="sa">f</span><span class="s2">"welcome to </span><span class="si">{</span><span class="n">room</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Dependencies behave just as they do for HTTP routes: providers resolve by
|
||
parameter name, can depend on one another, and are torn down when the
|
||
connection closes. A provider reaches the live socket by naming a
|
||
parameter <code class="docutils literal notranslate"><span class="pre">ws</span></code> (or <code class="docutils literal notranslate"><span class="pre">websocket</span></code>) or annotating it <code class="docutils literal notranslate"><span class="pre">WebSocket</span></code>; the
|
||
names <code class="docutils literal notranslate"><span class="pre">req</span></code>, <code class="docutils literal notranslate"><span class="pre">request</span></code>, <code class="docutils literal notranslate"><span class="pre">resp</span></code>, <code class="docutils literal notranslate"><span class="pre">response</span></code>, <code class="docutils literal notranslate"><span class="pre">ws</span></code>, and
|
||
<code class="docutils literal notranslate"><span class="pre">websocket</span></code> are reserved and can’t be used as dependency names. See the
|
||
Dependency Injection section of the <a class="reference internal" href="tour.html"><span class="doc">feature tour</span></a> for the
|
||
full picture.</p>
|
||
<p>Parameter markers work on WebSocket handlers too. <a class="reference internal" href="api.html#responder.Query" title="responder.Query"><code class="xref py py-func docutils literal notranslate"><span class="pre">Query()</span></code></a>,
|
||
<a class="reference internal" href="api.html#responder.Header" title="responder.Header"><code class="xref py py-func docutils literal notranslate"><span class="pre">Header()</span></code></a>, <a class="reference internal" href="api.html#responder.Cookie" title="responder.Cookie"><code class="xref py py-func docutils literal notranslate"><span class="pre">Cookie()</span></code></a>, and
|
||
<a class="reference internal" href="api.html#responder.Path" title="responder.Path"><code class="xref py py-func docutils literal notranslate"><span class="pre">Path()</span></code></a> resolve from the handshake request — the query
|
||
string, headers, and cookies — with the same Pydantic coercion and defaults
|
||
as HTTP views. This is the natural fit for token-in-query-string auth,
|
||
the standard browser pattern (browsers can’t set custom headers on a
|
||
WebSocket handshake):</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder</span><span class="w"> </span><span class="kn">import</span> <span class="n">Query</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/live"</span><span class="p">,</span> <span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">live</span><span class="p">(</span><span class="n">ws</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">token</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="o">...</span><span class="p">),</span> <span class="n">room</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="mi">0</span><span class="p">)):</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">send_json</span><span class="p">({</span><span class="s2">"room"</span><span class="p">:</span> <span class="n">room</span><span class="p">})</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>A missing required marker or a failed coercion closes the connection with
|
||
code <code class="docutils literal notranslate"><span class="pre">1008</span></code> (policy violation) before the handler runs — the WebSocket
|
||
equivalent of the <code class="docutils literal notranslate"><span class="pre">422</span></code> an HTTP view returns. (<code class="docutils literal notranslate"><span class="pre">Form()</span></code>/<code class="docutils literal notranslate"><span class="pre">File()</span></code>
|
||
markers resolve as missing on WebSocket handlers, which have no body.)</p>
|
||
</section>
|
||
<section id="connection-lifecycle">
|
||
<h2>Connection Lifecycle<a class="headerlink" href="#connection-lifecycle" title="Link to this heading">¶</a></h2>
|
||
<p>WebSocket connections go through several states:</p>
|
||
<ol class="arabic simple">
|
||
<li><p><strong>Connecting</strong> — the client sends an upgrade request</p></li>
|
||
<li><p><strong>Open</strong> — after <code class="docutils literal notranslate"><span class="pre">await</span> <span class="pre">ws.accept()</span></code>, both sides can send messages</p></li>
|
||
<li><p><strong>Closing</strong> — either side initiates a close handshake</p></li>
|
||
<li><p><strong>Closed</strong> — the connection is fully terminated</p></li>
|
||
</ol>
|
||
<p>When a client disconnects (closes the tab, loses network), the next
|
||
<code class="docutils literal notranslate"><span class="pre">await</span> <span class="pre">ws.receive_text()</span></code> raises <code class="docutils literal notranslate"><span class="pre">WebSocketDisconnect</span></code>. Always
|
||
handle this — otherwise your server accumulates dead connections:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">starlette.websockets</span><span class="w"> </span><span class="kn">import</span> <span class="n">WebSocketDisconnect</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/ws"</span><span class="p">,</span> <span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">handler</span><span class="p">(</span><span class="n">ws</span><span class="p">):</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">receive_text</span><span class="p">()</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">send_text</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Got: </span><span class="si">{</span><span class="n">data</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||
<span class="k">except</span> <span class="n">WebSocketDisconnect</span><span class="p">:</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s2">"Client disconnected"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>You can also close connections from the server side:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">close</span><span class="p">(</span><span class="n">code</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span> <span class="c1"># 1000 = normal closure</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Common close codes: <code class="docutils literal notranslate"><span class="pre">1000</span></code> (normal), <code class="docutils literal notranslate"><span class="pre">1001</span></code> (going away),
|
||
<code class="docutils literal notranslate"><span class="pre">1008</span></code> (policy violation), <code class="docutils literal notranslate"><span class="pre">1011</span></code> (server error).</p>
|
||
<p>When a connection holds a resource — a database session, a lock, a slot
|
||
in a presence registry — reach for a generator dependency instead of a
|
||
hand-rolled <code class="docutils literal notranslate"><span class="pre">try/finally</span></code>. Responder runs the teardown after <code class="docutils literal notranslate"><span class="pre">yield</span></code>
|
||
when the handler returns <em>or</em> when the client disconnects, so cleanup is
|
||
guaranteed even if the socket drops mid-stream:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">dependency</span><span class="p">()</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">db</span><span class="p">():</span>
|
||
<span class="n">conn</span> <span class="o">=</span> <span class="k">await</span> <span class="n">pool</span><span class="o">.</span><span class="n">acquire</span><span class="p">()</span>
|
||
<span class="k">yield</span> <span class="n">conn</span>
|
||
<span class="k">await</span> <span class="n">pool</span><span class="o">.</span><span class="n">release</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span> <span class="c1"># runs when the connection closes</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/feed"</span><span class="p">,</span> <span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">feed</span><span class="p">(</span><span class="n">ws</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">db</span><span class="p">):</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
|
||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||
<span class="n">row</span> <span class="o">=</span> <span class="k">await</span> <span class="n">db</span><span class="o">.</span><span class="n">fetch_next</span><span class="p">()</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">send_json</span><span class="p">(</span><span class="n">row</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="testing-websockets">
|
||
<h2>Testing WebSockets<a class="headerlink" href="#testing-websockets" title="Link to this heading">¶</a></h2>
|
||
<p>Responder’s test client — <code class="docutils literal notranslate"><span class="pre">api.requests</span></code>, a Starlette <code class="docutils literal notranslate"><span class="pre">TestClient</span></code> —
|
||
speaks WebSocket too. <code class="docutils literal notranslate"><span class="pre">websocket_connect</span></code> is a context manager that
|
||
opens the connection on enter and closes it on exit:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_echo</span><span class="p">():</span>
|
||
<span class="k">with</span> <span class="n">api</span><span class="o">.</span><span class="n">requests</span><span class="o">.</span><span class="n">websocket_connect</span><span class="p">(</span><span class="s2">"/ws"</span><span class="p">)</span> <span class="k">as</span> <span class="n">ws</span><span class="p">:</span>
|
||
<span class="n">ws</span><span class="o">.</span><span class="n">send_text</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="n">ws</span><span class="o">.</span><span class="n">receive_text</span><span class="p">()</span> <span class="o">==</span> <span class="s2">"Echo: hello"</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>You can also assert that a connection is rejected:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.websockets</span><span class="w"> </span><span class="kn">import</span> <span class="n">WebSocketDisconnect</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">test_rejects_unknown_route</span><span class="p">():</span>
|
||
<span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="n">WebSocketDisconnect</span><span class="p">):</span>
|
||
<span class="k">with</span> <span class="n">api</span><span class="o">.</span><span class="n">requests</span><span class="o">.</span><span class="n">websocket_connect</span><span class="p">(</span><span class="s2">"/nonexistent"</span><span class="p">):</span>
|
||
<span class="k">pass</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>See <a class="reference internal" href="testing.html"><span class="doc">the testing guide</span></a> for fixtures and the rest of the
|
||
in-process testing story.</p>
|
||
</section>
|
||
</section>
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||
<div class="sphinxsidebarwrapper"><p class="logo">
|
||
<a href="index.html">
|
||
<img class="logo" src="_static/responder.png" />
|
||
</a>
|
||
</p>
|
||
<p>
|
||
<strong>Responder</strong> — a familiar HTTP service framework for Python.
|
||
<br />
|
||
<small>v9.2.0</small>
|
||
</p>
|
||
<h3>Useful Links</h3>
|
||
<ul>
|
||
<li><a href="https://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
||
<li><a href="https://pypi.org/project/responder/">Responder @ PyPI</a></li>
|
||
<li><a href="https://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||
</ul><h3>Navigation</h3>
|
||
<p class="caption" role="heading"><span class="caption-text">User Guide</span></p>
|
||
<ul>
|
||
<li class="toctree-l1"><a class="reference internal" href="quickstart.html">Quick Start</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tour.html">Feature Tour</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Examples</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="routers.html">Composing Apps with Routers</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="guide-config.html">Configuration</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="clientgen.html">Generated Clients</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="deployment.html">Deployment</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="testing.html">Testing</a></li>
|
||
</ul>
|
||
<p class="caption" role="heading"><span class="caption-text">Tutorials</span></p>
|
||
<ul class="current">
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-rest.html">Building a REST API</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-sqlalchemy.html">Using SQLAlchemy</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-auth.html">Authentication</a></li>
|
||
<li class="toctree-l1 current"><a class="current reference internal" href="#">WebSocket Tutorial</a><ul>
|
||
<li class="toctree-l2"><a class="reference internal" href="#how-websockets-work">How WebSockets Work</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#echo-server">Echo Server</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#chat-room">Chat Room</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#data-formats">Data Formats</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#html-client">HTML Client</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#before-request-hooks-for-websockets">Before-Request Hooks for WebSockets</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#connection-lifecycle">Connection Lifecycle</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#testing-websockets">Testing WebSockets</a></li>
|
||
</ul>
|
||
</li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-middleware.html">Writing Middleware</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-flask.html">Migrating from Flask</a></li>
|
||
</ul>
|
||
<p class="caption" role="heading"><span class="caption-text">Reference</span></p>
|
||
<ul>
|
||
<li class="toctree-l1"><a class="reference internal" href="api.html">API Reference</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="cli.html">Command Line Interface</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="runtime-contracts.html">Runtime Contracts</a></li>
|
||
</ul>
|
||
<p class="caption" role="heading"><span class="caption-text">Project</span></p>
|
||
<ul>
|
||
<li class="toctree-l1"><a class="reference internal" href="changes.html">Changelog</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v9.html">Migrating to v9</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v8.html">Migrating to v8</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v7.html">Migrating to v7</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v6.html">Migrating to v6</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v5.html">Migrating to v5</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="sandbox.html">Sandbox</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="backlog.html">Backlog</a></li>
|
||
</ul>
|
||
|
||
<div class="relations">
|
||
<h3>Related Topics</h3>
|
||
<ul>
|
||
<li><a href="index.html">Documentation overview</a><ul>
|
||
<li>Previous: <a href="tutorial-auth.html" title="previous chapter">Authentication</a></li>
|
||
<li>Next: <a href="tutorial-middleware.html" title="next chapter">Writing Middleware</a></li>
|
||
</ul></li>
|
||
</ul>
|
||
</div>
|
||
<search id="searchbox" style="display: none" role="search">
|
||
<h3 id="searchlabel">Quick search</h3>
|
||
<div class="searchformwrapper">
|
||
<form class="search" action="search.html" method="get">
|
||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||
<input type="submit" value="Go" />
|
||
</form>
|
||
</div>
|
||
</search>
|
||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
||
</div>
|
||
</div>
|
||
<div class="clearer"></div>
|
||
</div>
|
||
<div class="footer">
|
||
©2018-2026, Kenneth Reitz.
|
||
|
||
|
|
||
<a href="_sources/tutorial-websockets.rst.txt"
|
||
rel="nofollow">Page source</a>
|
||
</div>
|
||
|
||
|
||
<a href="https://github.com/kennethreitz/responder" class="github">
|
||
<img src="_static/forkme_right_darkblue.png" alt="Fork me on GitHub" class="github"/>
|
||
</a>
|
||
|
||
|
||
|
||
</body>
|
||
</html> |