<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>websocket · wleeaf field notes</title><link>https://articles.wleeaf.dev/tags/websocket/</link><description>Hands-on guides and build logs from the wleeaf studio: self-hosting, Linux, networking, Docker, the command line and reverse engineering.</description><language>en-us</language><lastBuildDate>Sun, 05 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://articles.wleeaf.dev/tags/websocket/feed.xml" rel="self" type="application/rss+xml"/><item><title>Building a WebSocket Server From Scratch in Node.js</title><link>https://articles.wleeaf.dev/posts/websocket-from-scratch/</link><pubDate>Sun, 05 Apr 2026 00:00:00 +0000</pubDate><guid>https://articles.wleeaf.dev/posts/websocket-from-scratch/</guid><description>The WebSocket protocol is simple enough to implement by hand. Here is what happens under the hood.</description><category>node.js</category><category>websocket</category><category>networking</category><content:encoded><![CDATA[<p>Libraries like Socket.IO and ws abstract away the WebSocket protocol. That is usually fine. But understanding what happens at the protocol level makes debugging easier and helps you make better architecture decisions.</p>
<p>Let us build a WebSocket server from scratch using only the Node.js standard library.</p>
<h2 id="the-handshake">The Handshake<a class="anchor" href="#the-handshake" aria-label="Link to “The Handshake”">#</a></h2>
<p>WebSocket starts as an HTTP upgrade request. The client sends:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>GET / HTTP/1.1
Host: localhost:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13</code></pre>
</div>
<p>The server responds:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=</code></pre>
</div>
<p>The <code>Sec-WebSocket-Accept</code> value is computed from the client&rsquo;s key:</p>
<div class="code">
<div class="code-head"><span class="code-lang">javascript</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">crypto</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;crypto&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">computeAcceptKey</span><span class="p">(</span><span class="nx">clientKey</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">MAGIC</span> <span class="o">=</span> <span class="s1">&#39;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">crypto</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">createHash</span><span class="p">(</span><span class="s1">&#39;sha1&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">clientKey</span> <span class="o">+</span> <span class="nx">MAGIC</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">digest</span><span class="p">(</span><span class="s1">&#39;base64&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
</div>
<p>The magic string is literally part of the RFC. Every WebSocket implementation uses it.</p>
<h2 id="the-server">The Server<a class="anchor" href="#the-server" aria-label="Link to “The Server”">#</a></h2>
<div class="code">
<div class="code-head"><span class="code-lang">javascript</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">http</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;http&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">crypto</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;crypto&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">MAGIC</span> <span class="o">=</span> <span class="s1">&#39;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">server</span> <span class="o">=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">createServer</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">clients</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Set</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">server</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;upgrade&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">socket</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">clientKey</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">headers</span><span class="p">[</span><span class="s1">&#39;sec-websocket-key&#39;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">clientKey</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">socket</span><span class="p">.</span><span class="nx">destroy</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">acceptKey</span> <span class="o">=</span> <span class="nx">crypto</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">createHash</span><span class="p">(</span><span class="s1">&#39;sha1&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">clientKey</span> <span class="o">+</span> <span class="nx">MAGIC</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">digest</span><span class="p">(</span><span class="s1">&#39;base64&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">socket</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;HTTP/1.1 101 Switching Protocols\r\n&#39;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;Upgrade: websocket\r\n&#39;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;Connection: Upgrade\r\n&#39;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">    <span class="sb">`Sec-WebSocket-Accept: </span><span class="si">${</span><span class="nx">acceptKey</span><span class="si">}</span><span class="sb">\r\n`</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;\r\n&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">clients</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">socket</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`Client connected (</span><span class="si">${</span><span class="nx">clients</span><span class="p">.</span><span class="nx">size</span><span class="si">}</span><span class="sb"> total)`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">socket</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;data&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">buffer</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">message</span> <span class="o">=</span> <span class="nx">decodeFrame</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">message</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Received:&#39;</span><span class="p">,</span> <span class="nx">message</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Echo to all clients
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">client</span> <span class="k">of</span> <span class="nx">clients</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">client</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">encodeFrame</span><span class="p">(</span><span class="nx">message</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">socket</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;close&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">clients</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">socket</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`Client disconnected (</span><span class="si">${</span><span class="nx">clients</span><span class="p">.</span><span class="nx">size</span><span class="si">}</span><span class="sb"> total)`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">socket</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;error&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">clients</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">socket</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">server</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="mi">8080</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;WebSocket server on ws://localhost:8080&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div>
</div>
<h2 id="frame-decoding">Frame Decoding<a class="anchor" href="#frame-decoding" aria-label="Link to “Frame Decoding”">#</a></h2>
<p>WebSocket messages are wrapped in frames. The frame format:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code> 0               1               2               3
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |            (16/64)            |
|N|V|V|V|       |S|             |                               |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+-------------------------------+
|     Masking key (if MASK set)                                 |
+-------------------------------+-------------------------------+
|     Payload Data                                              |
+---------------------------------------------------------------+</code></pre>
</div>
<p>Client-to-server frames are always masked. Server-to-client frames are never masked. Here is the decoder:</p>
<div class="code">
<div class="code-head"><span class="code-lang">javascript</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">decodeFrame</span><span class="p">(</span><span class="nx">buffer</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">firstByte</span> <span class="o">=</span> <span class="nx">buffer</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">opcode</span> <span class="o">=</span> <span class="nx">firstByte</span> <span class="o">&amp;</span> <span class="mh">0x0f</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// 0x1 = text, 0x8 = close, 0x9 = ping, 0xA = pong
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="k">if</span> <span class="p">(</span><span class="nx">opcode</span> <span class="o">===</span> <span class="mh">0x8</span><span class="p">)</span> <span class="k">return</span> <span class="kc">null</span><span class="p">;</span> <span class="c1">// close frame
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">secondByte</span> <span class="o">=</span> <span class="nx">buffer</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">isMasked</span> <span class="o">=</span> <span class="p">(</span><span class="nx">secondByte</span> <span class="o">&amp;</span> <span class="mh">0x80</span><span class="p">)</span> <span class="o">!==</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kd">let</span> <span class="nx">payloadLength</span> <span class="o">=</span> <span class="nx">secondByte</span> <span class="o">&amp;</span> <span class="mh">0x7f</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kd">let</span> <span class="nx">offset</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">payloadLength</span> <span class="o">===</span> <span class="mi">126</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">payloadLength</span> <span class="o">=</span> <span class="nx">buffer</span><span class="p">.</span><span class="nx">readUInt16BE</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">offset</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">payloadLength</span> <span class="o">===</span> <span class="mi">127</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">payloadLength</span> <span class="o">=</span> <span class="nb">Number</span><span class="p">(</span><span class="nx">buffer</span><span class="p">.</span><span class="nx">readBigUInt64BE</span><span class="p">(</span><span class="mi">2</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="nx">offset</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">let</span> <span class="nx">maskingKey</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">isMasked</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">maskingKey</span> <span class="o">=</span> <span class="nx">buffer</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">offset</span><span class="p">,</span> <span class="nx">offset</span> <span class="o">+</span> <span class="mi">4</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">offset</span> <span class="o">+=</span> <span class="mi">4</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">payload</span> <span class="o">=</span> <span class="nx">buffer</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">offset</span><span class="p">,</span> <span class="nx">offset</span> <span class="o">+</span> <span class="nx">payloadLength</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">isMasked</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">payload</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">payload</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">^=</span> <span class="nx">maskingKey</span><span class="p">[</span><span class="nx">i</span> <span class="o">%</span> <span class="mi">4</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">payload</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
</div>
<h2 id="frame-encoding">Frame Encoding<a class="anchor" href="#frame-encoding" aria-label="Link to “Frame Encoding”">#</a></h2>
<p>Sending from server to client is simpler because we do not mask:</p>
<div class="code">
<div class="code-head"><span class="code-lang">javascript</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">encodeFrame</span><span class="p">(</span><span class="nx">message</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">payload</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">message</span><span class="p">,</span> <span class="s1">&#39;utf-8&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">length</span> <span class="o">=</span> <span class="nx">payload</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">let</span> <span class="nx">header</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">length</span> <span class="o">&lt;</span> <span class="mi">126</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">header</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="nx">alloc</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">header</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x81</span><span class="p">;</span> <span class="c1">// FIN + text opcode
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">header</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nx">length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">length</span> <span class="o">&lt;</span> <span class="mi">65536</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">header</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="nx">alloc</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">header</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x81</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">header</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">126</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">header</span><span class="p">.</span><span class="nx">writeUInt16BE</span><span class="p">(</span><span class="nx">length</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">header</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="nx">alloc</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">header</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x81</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">header</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">127</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">header</span><span class="p">.</span><span class="nx">writeBigUInt64BE</span><span class="p">(</span><span class="nx">BigInt</span><span class="p">(</span><span class="nx">length</span><span class="p">),</span> <span class="mi">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">Buffer</span><span class="p">.</span><span class="nx">concat</span><span class="p">([</span><span class="nx">header</span><span class="p">,</span> <span class="nx">payload</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
</div>
<h2 id="testing-it">Testing It<a class="anchor" href="#testing-it" aria-label="Link to “Testing It”">#</a></h2>
<p>Open a browser console:</p>
<div class="code">
<div class="code-head"><span class="code-lang">javascript</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ws</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebSocket</span><span class="p">(</span><span class="s1">&#39;ws://localhost:8080&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">ws</span><span class="p">.</span><span class="nx">onmessage</span> <span class="o">=</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Got:&#39;</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">ws</span><span class="p">.</span><span class="nx">onopen</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="nx">ws</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="s1">&#39;hello from browser&#39;</span><span class="p">);</span></span></span></code></pre></div>
</div>
<p>Or use a Node.js client:</p>
<div class="code">
<div class="code-head"><span class="code-lang">javascript</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">net</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;net&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">crypto</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;crypto&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">key</span> <span class="o">=</span> <span class="nx">crypto</span><span class="p">.</span><span class="nx">randomBytes</span><span class="p">(</span><span class="mi">16</span><span class="p">).</span><span class="nx">toString</span><span class="p">(</span><span class="s1">&#39;base64&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">socket</span> <span class="o">=</span> <span class="nx">net</span><span class="p">.</span><span class="nx">createConnection</span><span class="p">(</span><span class="mi">8080</span><span class="p">,</span> <span class="s1">&#39;localhost&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">socket</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;GET / HTTP/1.1\r\n&#39;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;Host: localhost:8080\r\n&#39;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;Upgrade: websocket\r\n&#39;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;Connection: Upgrade\r\n&#39;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">    <span class="sb">`Sec-WebSocket-Key: </span><span class="si">${</span><span class="nx">key</span><span class="si">}</span><span class="sb">\r\n`</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;Sec-WebSocket-Version: 13\r\n&#39;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;\r\n&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div>
</div>
<h2 id="what-this-does-not-handle">What This Does Not Handle<a class="anchor" href="#what-this-does-not-handle" aria-label="Link to “What This Does Not Handle”">#</a></h2>
<p>This implementation is deliberately minimal. A production WebSocket server also needs:</p>
<ul>
<li><strong>Ping/pong frames</strong> for connection keepalive</li>
<li><strong>Fragmented messages</strong> where large payloads span multiple frames</li>
<li><strong>Close handshake</strong> with proper status codes</li>
<li><strong>Per-message compression</strong> (the permessage-deflate extension)</li>
<li><strong>Backpressure</strong> when clients read slower than you write</li>
</ul>
<p>That is why libraries like ws exist. But knowing the protocol means you understand what those libraries are doing, and you can debug problems at the frame level instead of guessing.</p>
<p>The entire WebSocket protocol fits in RFC 6455, which is surprisingly readable as RFCs go. The core framing logic is maybe 100 lines of code. Everything else is just handling edge cases.</p>
]]></content:encoded></item></channel></rss>