Files
responder/tutorial-websockets.html
2026-07-14 04:14:56 +00:00

419 lines
39 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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 &#8212; 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">&quot;/ws&quot;</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">&quot;Echo: </span><span class="si">{</span><span class="n">data</span><span class="si">}</span><span class="s2">&quot;</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 &quot;
import websocket
ws = websocket.create_connection(&#39;ws://localhost:5042/ws&#39;)
ws.send(&#39;hello&#39;)
print(ws.recv()) # Echo: hello
ws.close()
&quot;
</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">&quot;/chat&quot;</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">&quot;hello&quot;</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">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;update&quot;</span><span class="p">,</span> <span class="s2">&quot;data&quot;</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">&quot;</span><span class="se">\x00\x01\x02</span><span class="s2">&quot;</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">&lt;!DOCTYPE html&gt;</span>
<span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&quot;messages&quot;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">input</span> <span class="na">id</span><span class="o">=</span><span class="s">&quot;input&quot;</span> <span class="na">placeholder</span><span class="o">=</span><span class="s">&quot;Type a message...&quot;</span> <span class="p">/&gt;</span>
<span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</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">&quot;ws://localhost:5042/chat&quot;</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">&quot;messages&quot;</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">&quot;input&quot;</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">=&gt;</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">&quot;p&quot;</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">&quot;keypress&quot;</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">=&gt;</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">&quot;Enter&quot;</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">&quot;&quot;</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">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</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">&quot;token&quot;</span><span class="p">)</span> <span class="o">!=</span> <span class="s2">&quot;secret&quot;</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">&quot;/chat/</span><span class="si">{room}</span><span class="s2">&quot;</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">&quot;welcome to </span><span class="si">{</span><span class="n">room</span><span class="si">}</span><span class="s2">&quot;</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">&quot;/live&quot;</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">&quot;room&quot;</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">&quot;/ws&quot;</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">&quot;Got: </span><span class="si">{</span><span class="n">data</span><span class="si">}</span><span class="s2">&quot;</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">&quot;Client disconnected&quot;</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">&quot;/feed&quot;</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">&quot;/ws&quot;</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">&quot;hello&quot;</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">&quot;Echo: hello&quot;</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">&quot;/nonexistent&quot;</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">
&#169;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>