<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>kmcd.dev</title><link>https://kmcd.dev/posts/</link><description>Recent content on kmcd.dev</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>All Rights Reserved</copyright><atom:link href="https://kmcd.dev/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Unknown Fields in Protobuf</title><link>https://kmcd.dev/posts/protobuf-unknown-fields/</link><pubDate>Thu, 16 Apr 2026 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/protobuf-unknown-fields/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/protobuf-unknown-fields/cover.svg" /> &lt;/p>
                
                How Protobuf unknown fields enable seamless schema evolution and robust middleware.
                </description><content:encoded><![CDATA[<div class="disclaimer">
    This article was originally published in March 2024. It was republished in April 2026 after some significant editing and modernization.
</div>

<p><a href="https://protobuf.dev/programming-guides/proto3/" rel="external">Protobuf</a> includes a feature known as <a href="https://protobuf.dev/programming-guides/proto3/#unknowns" rel="external"><strong>unknown fields</strong></a>. They act as a safety net when systems encounter data they weren&rsquo;t explicitly built to handle. Here is a breakdown of what they are and why they matter.</p>
<h2 id="what-are-protobuf-unknown-fields">What are Protobuf Unknown Fields?</h2>
<p>Your <code>.proto</code> file defines the expected structure, fields, and data types. But what happens when you parse a message and it contains fields that aren&rsquo;t in your current <code>.proto</code> definition?</p>
<p>These extra pieces of data are called <strong>unknown fields</strong>.</p>
<p>At a lower level, unknown fields are <strong>field numbers and wire types that exist in the serialized message but are not defined in the current schema</strong>.</p>
<p>This mechanism is what enables <strong>forward compatibility</strong>: an older version of your software can safely read, process, and forward data produced by a newer version of the schema without crashing or losing the new data.</p>
<blockquote>
<p><strong>Key idea:</strong> Unknown fields enable forward compatibility by default.</p>
</blockquote>
<hr>
<h2 id="preserving-unknown-data">Preserving Unknown Data</h2>
<p>A key aspect of unknown fields is how they behave during message manipulation.</p>
<p>If you receive a message with unknown fields and forward it to another system, Protobuf defaults to <strong>forwarding the unknown fields alongside the known ones</strong>. This ensures the receiving system gets the complete payload.</p>
<p>If this didn&rsquo;t happen, you could accidentally clear field values set by another part of the system.</p>
<p>This forwarding capability also applies when <strong>persisting messages</strong>, as long as they remain in <strong>binary Protobuf format</strong>. If you store and later reload the binary payload, the unknown fields are preserved.</p>
<blockquote>
<p><strong>Key idea:</strong> Binary Protobuf preserves unknown fields end-to-end.</p>
</blockquote>
<blockquote>
<p><strong>Historical Note:</strong> Protobuf v3 initially tried to simplify the specification by removing several proto2 features, but real-world usage forced them to walk the biggest ones back. Early versions of proto3 dropped unknown fields entirely, but this was reversed in v3.5. Similarly, proto3 initially removed the <code>optional</code> keyword, but brought it back in v3.15 after developers struggled to distinguish between a field being unset and a field just having a zero value, which is <a href="https://en.wikipedia.org/wiki/Null_Island" rel="external">a classic programming mistake</a>.</p>
</blockquote>
<hr>
<h2 id="comparison-to-json">Comparison to JSON</h2>
<p>Consider a scenario where a new field, <code>email</code>, is added to a user object. The backend is updated, but the frontend is not.</p>
<p>The issue in JSON systems is not JSON itself, it&rsquo;s <strong>typed deserialization</strong>.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;user&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;id&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;0edc0903-9e31-47be-adad-1dfc434ca2d3&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;name&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Bob&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;email&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;bob@example.com&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>If the frontend maps this into a typed structure:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">class</span> User <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  id: <span style="color:#81a1c1">string</span><span style="color:#eceff4">;</span>
</span></span><span style="display:flex;"><span>  name: <span style="color:#81a1c1">string</span><span style="color:#eceff4">;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>The unknown field (<code>email</code>) is dropped during deserialization. When the object is sent back:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;user&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;id&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;0edc0903-9e31-47be-adad-1dfc434ca2d3&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;name&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Bob&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>The <code>email</code> field is lost.</p>
<blockquote>
<p><strong>Key idea:</strong> Typed JSON pipelines often drop unknown fields during reserialization.</p>
</blockquote>
<hr>
<h2 id="protobuf-behavior">Protobuf Behavior</h2>
<p>With Protobuf, the same scenario behaves differently.</p>
<p>Even if the frontend does not know about the <code>email</code> field, it is preserved internally:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span>Symbol<span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">@bufbuild</span><span style="color:#81a1c1">/</span>protobuf<span style="color:#81a1c1">/</span><span style="color:#81a1c1">unknown</span><span style="color:#81a1c1">-</span>fields<span style="color:#eceff4">)</span><span style="color:#81a1c1">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">{</span><span style="color:#b48ead">0</span><span style="color:#81a1c1">:</span> <span style="color:#eceff4">{</span>no:<span style="color:#81a1c1">3</span><span style="color:#eceff4">,</span> wire_type:<span style="color:#81a1c1">2</span><span style="color:#eceff4">,</span> data: <span style="color:#81a1c1">Uint8Array</span><span style="color:#eceff4">(</span><span style="color:#b48ead">14</span><span style="color:#eceff4">)}}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">]</span>
</span></span></code></pre></div><p><em>(Note: This specific <code>Symbol</code> representation is how the <code>@bufbuild/protobuf</code> implementation manages it under the hood. Other JS/TS generators might expose this data slightly differently, but the underlying concept remains the same.)</em></p>
<ul>
<li><code>no: 3</code> → field number (email)</li>
<li><code>wire_type: 2</code> → length-delimited (used for strings)</li>
<li><code>data</code> → raw encoded value</li>
</ul>
<p>When the message is re-encoded:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>1:LEN {&#34;0edc0903-9e31-47be-adad-1dfc434ca2d3&#34;}
</span></span><span style="display:flex;"><span>2:LEN {&#34;Bob&#34;}
</span></span><span style="display:flex;"><span>3:LEN {&#34;bob@example.com&#34;}
</span></span></code></pre></div><p>The unknown field survives the round trip.</p>
<blockquote>
<p><strong>Key idea:</strong> Unknown fields are preserved even when not understood.</p>
</blockquote>
<hr>
<h2 id="why-we-cant-guess-the-type">Why We Can&rsquo;t &ldquo;Guess&rdquo; the Type</h2>
<p>You might wonder why we can&rsquo;t just look at the raw data in an unknown field and &ldquo;guess&rdquo; what it is. The reason is that Protobuf uses a very limited set of <strong>wire types</strong> that are shared across many different high-level data types.</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Wire Type</th>
          <th style="text-align: left">Meaning</th>
          <th style="text-align: left">Used for&hellip;</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>0</code></td>
          <td style="text-align: left">Varint</td>
          <td style="text-align: left"><code>int32</code>, <code>int64</code>, <code>uint32</code>, <code>uint64</code>, <code>sint32</code>, <code>sint64</code>, <code>bool</code>, <code>enum</code></td>
      </tr>
      <tr>
          <td style="text-align: left"><code>1</code></td>
          <td style="text-align: left">64-bit</td>
          <td style="text-align: left"><code>fixed64</code>, <code>sfixed64</code>, <code>double</code></td>
      </tr>
      <tr>
          <td style="text-align: left"><code>2</code></td>
          <td style="text-align: left">Length-delimited</td>
          <td style="text-align: left"><code>string</code>, <code>bytes</code>, embedded messages, packed repeated fields</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>5</code></td>
          <td style="text-align: left">32-bit</td>
          <td style="text-align: left"><code>fixed32</code>, <code>sfixed32</code>, <code>float</code></td>
      </tr>
  </tbody>
</table>
<p>If you see an unknown field with <strong>Wire Type 0</strong>, you don&rsquo;t know if it represents the number <code>150</code>, the boolean <code>true</code>, or an enum value.</p>
<p>More critically, if you see <strong>Wire Type 2</strong>, you have no way of knowing if the data is a <code>string</code>, a <code>bytes</code> buffer, or a complex nested message. If you try to &ldquo;guess&rdquo; and parse a string as a sub-message, you will likely get gibberish or a parsing error. This is why the schema is strictly required to <em>understand</em> the data, while the binary format is sufficient to <em>preserve</em> it.</p>
<hr>
<h2 id="the-middleware-advantage">The Middleware Advantage</h2>
<p>Unknown fields shine in internal, middleware-heavy architectures.</p>
<p>Example:</p>
<ul>
<li>API Gateway reads <code>id</code> for routing</li>
<li>Logging service reads <code>trace_id</code></li>
<li>Downstream service understands full schema including new fields</li>
</ul>
<p>Intermediate services can safely:</p>
<ol>
<li>Unmarshal using an older schema</li>
<li>Read known fields</li>
<li>Forward the message unchanged</li>
</ol>
<p>No coordination is required when new fields are added upstream.</p>
<blockquote>
<p><strong>Key idea:</strong> Internal middleware can stay stable while schemas evolve.</p>
</blockquote>
<hr>
<h2 id="observability-a-signal-for-upgrades">Observability: A Signal for Upgrades</h2>
<p>Beyond just forwarding data safely, unknown fields provide a highly valuable observability metric.</p>
<p>When an API gateway or a downstream service detects unknown fields in incoming payloads, it is a clear telemetry signal: a client or upstream service is sending extra information because it is using a newer schema.</p>
<p>Instead of crashing or silently dropping the data, the service can log the presence of these unknown fields. You can use this data to trigger alerts, track the rollout progress of new features across your architecture, and pinpoint exactly which legacy services are lagging behind and due for an upgrade.</p>
<hr>
<h2 id="a-note-on-json-serialization-and-object-re-use">A Note on JSON Serialization and Object Re-use</h2>
<p>There are a couple of important exceptions where unknown fields get lost.</p>
<p>First, unknown field preservation applies <strong>only to binary Protobuf serialization</strong>. If you convert from binary to JSON (e.g., using <code>protojson</code> in Go or <code>toJson</code> in TypeScript), unknown fields are <strong>dropped</strong> during the encoding process. When unmarshaling JSON back into Protobuf, many libraries are strictly configured by default. For example, Go&rsquo;s <code>protojson.Unmarshal</code> will throw a hard error if it encounters unknown fields in the JSON payload unless you explicitly bypass it by passing <code>DiscardUnknown: true</code>. JSON simply isn&rsquo;t designed to carry this extra payload without a strict schema map.</p>
<p>Second, preserving these fields during binary serialization requires that you re-use the exact same object for re-serialization. If you read a message, pull out the known fields, and map them into a freshly created object to send downstream, the unknown fields tied to the original object will be left behind.</p>
<blockquote>
<p><strong>Key idea:</strong> Binary preserves and JSON drops. Always re-use the original object if you want to keep unknown fields intact.</p>
</blockquote>
<hr>
<h2 id="databases-and-security">Databases and Security</h2>
<p>The theoretical elegance of unknown fields often collides with the messy reality of databases and security perimeters. In practice, relying on unknown fields breaks down entirely in a few critical scenarios.</p>
<p>First, let us consider database persistence. If clients are trying to store extra data, and a backend service parses a Protobuf message to map it to standard relational database columns, those unknown fields are absolutely gone. There is no magic column for data your database schema does not know about.</p>
<p>The only way to achieve true end-to-end preservation is to store the entire serialized Protobuf message directly in the database as a BLOB. Some teams do this, but blindly storing data you haven&rsquo;t validated and don&rsquo;t even recognize is highly dangerous.</p>
<p>Allowing unknown fields to propagate unchecked from external sources is a significant security risk. While they are a powerful tool inside clearly defined, trusted internal pipelines, accepting them from the open web opens your system up to data smuggling. It allows malicious actors to sneak unvalidated payloads into unknown fields to bypass validation layers that only inspect known schema structures. If your systems blindly unmarshal, store, and forward this data, older services act as unwitting mules for malicious input.</p>
<p>Because of these exact risks, the standard security posture is to aggressively filter at the edge. API Gateways and ingress proxies should explicitly discard unknown fields before the data ever reaches internal microservices.</p>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>Unknown fields provide a powerful mechanism for <strong>forward compatibility</strong> in distributed systems. They allow internal systems to evolve independently, act as a clear signal for required upgrades, reduce coordination overhead, and simplify middleware design.</p>
<p>However, they are not a substitute for validation, schema discipline, or proper security boundaries. Use them intentionally in trusted internal pipelines, but never trust them at the edge.</p>
]]></content:encoded></item><item><title>IRC Log: Reactionary</title><link>https://kmcd.dev/posts/irc-log-reactionary/</link><pubDate>Mon, 30 Mar 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/irc-log-reactionary/</guid><description><![CDATA[ 
                <p> <img hspace="5" src="https://kmcd.dev/posts/irc-log-reactionary/cover.svg" /> </p>
                
                A seamless session update turns every customer&#39;s browser into an infinite loop cannon.
                ]]></description><content:encoded><![CDATA[<div class="chat-log" style="font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; background-color: #222; color: #eee; padding: 15px; border-radius: 5px; overflow-x: auto; line-height: 1.4; margin-bottom: 20px; font-size: 0.9rem; border: 1px solid #444;">


<div>
  <font color="#777777">[14:15:00]</font> 
  <font color="#4caf50"><b>*** PagerDutyBot: SEV-0: GLOBAL CONTROL PLANE UNREACHABLE. API ERROR RATE &gt; 95%.</b></font>
</div>







<div>
  <font color="#777777">[14:15:05]</font> 
  
    <font color="#4caf50"><i>*** <font color="#ec407a"><b>sev_manager</b></font> has joined</i></font>
  
</div>






<div>
  <font color="#777777">[14:15:10]</font> 
  <font color="#ffa726"><b>&lt;sev_manager&gt;</b></font>
  <span>Status?</span>
</div>






<div>
  <font color="#777777">[14:16:12]</font> 
  <font color="#9ccc65"><b>&lt;net_ops_jen&gt;</b></font>
  <span>It’s bad. Traffic to the auth service just verticalized. We’re seeing 50M RPS.</span>
</div>






<div>
  <font color="#777777">[14:17:00]</font> 
  <font color="#ffa726"><b>&lt;sev_manager&gt;</b></font>
  <span>DDoS? Scrubbing center active?</span>
</div>






<div>
  <font color="#777777">[14:17:30]</font> 
  <font color="#9ccc65"><b>&lt;net_ops_jen&gt;</b></font>
  <span>That&rsquo;s the weird part. It&rsquo;s bypassing the WAF. It looks like legit traffic. TLS fingerprints are valid.</span>
</div>






<div>
  <font color="#777777">[14:18:10]</font> 
  <font color="#ffa726"><b>&lt;sev_manager&gt;</b></font>
  <span>Did someone let Tyler push a new WAF regex? Please tell me we didn&rsquo;t backpedal into 2019.</span>
</div>






<div>
  <font color="#777777">[14:19:00]</font> 
  <font color="#ff7043"><b>&lt;backend_bob&gt;</b></font>
  <span>I’m looking at the sample logs. These are all authenticated requests.</span>
</div>






<div>
  <font color="#777777">[14:19:45]</font> 
  <font color="#ff7043"><b>&lt;backend_bob&gt;</b></font>
  <span>They are all hitting <code>POST /api/v4/user/token/refresh</code>.</span>
</div>






<div>
  <font color="#777777">[14:20:10]</font> 
  <font color="#ffa726"><b>&lt;sev_manager&gt;</b></font>
  <span>Why is everyone refreshing their token at the exact same second?</span>
</div>






<div>
  <font color="#777777">[14:21:00]</font> 
  <font color="#9ccc65"><b>&lt;net_ops_jen&gt;</b></font>
  <span>It’s not one refresh. I’m seeing the SAME user IDs hitting it 500 times per second.</span>
</div>






<div>
  <font color="#777777">[14:22:15]</font> 
  <font color="#ff7043"><b>&lt;frontend_felix&gt;</b></font>
  <span>Uh, guys?</span>
</div>






<div>
  <font color="#777777">[14:22:30]</font> 
  <font color="#ffa726"><b>&lt;sev_manager&gt;</b></font>
  <span>Go ahead Felix.</span>
</div>






<div>
  <font color="#777777">[14:23:00]</font> 
  <font color="#ff7043"><b>&lt;frontend_felix&gt;</b></font>
  <span>We pushed the &ldquo;Seamless Session&rdquo; update to the dashboard 15 minutes ago.</span>
</div>






<div>
  <font color="#777777">[14:23:15]</font> 
  <font color="#ff7043"><b>&lt;frontend_felix&gt;</b></font>
  <span>The goal was to silently refresh the token in the background so users don&rsquo;t get logged out.</span>
</div>






<div>
  <font color="#777777">[14:24:00]</font> 
  <font color="#ff7043"><b>&lt;backend_bob&gt;</b></font>
  <span>Felix&hellip; look at the code.</span>
</div>






<div>
  <font color="#777777">[14:25:00]</font> 
  <font color="#ff7043"><b>&lt;frontend_felix&gt;</b></font>
  <span>I am.</span>
</div>






<div>
  <font color="#777777">[14:25:10]</font> 
  <font color="#ff7043"><b>&lt;frontend_felix&gt;</b></font>
  <span>&hellip;</span>
</div>






<div>
  <font color="#777777">[14:25:15]</font> 
  <font color="#ff7043"><b>&lt;backend_bob&gt;</b></font>
  <span>Felix?</span>
</div>






<div>
  <font color="#777777">[14:25:45]</font> 
  <font color="#ff7043"><b>&lt;frontend_felix&gt;</b></font>
  <span>Oh no.</span>
</div>






<div>
  <font color="#777777">[14:26:00]</font> 
  <font color="#ffa726"><b>&lt;sev_manager&gt;</b></font>
  <span>REPORT.</span>
</div>






<div>
  <font color="#777777">[14:26:30]</font> 
  <font color="#ff7043"><b>&lt;frontend_felix&gt;</b></font>
  <span>Okay, so, in React&hellip; <code>useEffect</code> runs when a dependency changes.</span>
</div>






<div>
  <font color="#777777">[14:26:45]</font> 
  <font color="#ff7043"><b>&lt;frontend_felix&gt;</b></font>
  <span>We have: <code>useEffect(() =&gt; { refreshToken() }, [token])</code></span>
</div>






<div>
  <font color="#777777">[14:27:00]</font> 
  <font color="#ff7043"><b>&lt;backend_bob&gt;</b></font>
  <span>And <code>refreshToken()</code>&hellip; updates the <code>token</code>?</span>
</div>






<div>
  <font color="#777777">[14:27:05]</font> 
  <font color="#ff7043"><b>&lt;frontend_felix&gt;</b></font>
  <span>Yes.</span>
</div>






<div>
  <font color="#777777">[14:27:10]</font> 
  <font color="#ff7043"><b>&lt;backend_bob&gt;</b></font>
  <span>Which triggers the <code>useEffect</code> again?</span>
</div>






<div>
  <font color="#777777">[14:27:12]</font> 
  <font color="#ff7043"><b>&lt;frontend_felix&gt;</b></font>
  <span>Yes.</span>
</div>






<div>
  <font color="#777777">[14:27:30]</font> 
  <font color="#ff7043"><b>&lt;backend_bob&gt;</b></font>
  <span>So you turned every single customer&rsquo;s browser tab into an infinite loop cannon pointed at our auth servers?</span>
</div>






<div>
  <font color="#777777">[14:28:00]</font> 
  <font color="#ff7043"><b>&lt;frontend_felix&gt;</b></font>
  <span>In my defense, the tokens are incredibly fresh.</span>
</div>






<div>
  <font color="#777777">[14:28:15]</font> 
  <font color="#ffa726"><b>&lt;sev_manager&gt;</b></font>
  <span>Rollback the frontend.</span>
</div>






<div>
  <font color="#777777">[14:29:10]</font> 
  <font color="#ffa726"><b>&lt;sev_manager&gt;</b></font>
  <span>I&rsquo;ll start writing another ridiculously well edited postmortem blog post.</span>
</div>






<div>
  <font color="#777777">[14:29:41]</font> 
  <font color="#ff7043"><b>&lt;backend_bob&gt;</b></font>
  <span>Hackernews and primeagen is going to going to love this one.</span>
</div>


</div>

<p><a href="https://blog.cloudflare.com/5-december-2025-outage/" rel="external">Cloudflare outage on December 5, 2025</a></p>
]]></content:encoded></item><item><title>Faking protobuf data in Go</title><link>https://kmcd.dev/posts/faking-protobuf-data-in-go/</link><pubDate>Thu, 26 Mar 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/faking-protobuf-data-in-go/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/faking-protobuf-data-in-go/cover.svg" /> &lt;/p>
                
                Stop hand-writing test fixtures
                </description><content:encoded><![CDATA[<p>If you build Go services that use protobuf over gRPC, you often need realistic-looking data. Sometimes you need a lot of it and your creative juices and manual data entry only goes so far. Tests, local demos, and stubs all benefit from messages that look plausible without carrying meaning.</p>
<p>FauxRPC addresses this. It’s a small Go library that fills protobuf messages with plausible data. The focus is not perfect realism; it’s speed, convenience, and reducing boilerplate.</p>
<p>This article covers two main use cases: populating generated protobuf types and working with dynamic descriptors. Along the way, you’ll see how to generate data and control its behavior for repeatable results.</p>
<h2 id="core-entry-points">Core entry points</h2>
<p>FauxRPC provides two primary functions:</p>
<ul>
<li><a href="https://pkg.go.dev/github.com/sudorandom/fauxrpc#SetDataOnMessage" rel="external"><code>fauxrpc.SetDataOnMessage</code></a>: Populates a concrete Go struct generated from <code>.proto</code> files.</li>
<li><a href="https://pkg.go.dev/github.com/sudorandom/fauxrpc#NewMessage" rel="external"><code>fauxrpc.NewMessage</code></a>: Creates a message from a <code>protoreflect.MessageDescriptor</code>, useful when types aren’t known at compile time.</li>
</ul>
<p>Both use the same underlying logic; the difference is whether you start with a Go struct or a descriptor.</p>
<h2 id="filling-a-generated-message">Filling a generated message</h2>
<p>For a simple case, consider the Eliza demo service and its <code>SayResponse</code> message.</p>
<details open>
    <summary>
        Eliza Service SayResponse Example (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/faking-protobuf-data-in-go/go/example1_say_response.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	elizav1 <span style="color:#a3be8c">&#34;buf.build/gen/go/connectrpc/eliza/protocolbuffers/go/connectrpc/eliza/v1&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;github.com/sudorandom/fauxrpc&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;google.golang.org/protobuf/encoding/protojson&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	msg <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>elizav1<span style="color:#eceff4">.</span>SayResponse<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> fauxrpc<span style="color:#eceff4">.</span><span style="color:#88c0d0">SetDataOnMessage</span><span style="color:#eceff4">(</span>msg<span style="color:#eceff4">,</span> fauxrpc<span style="color:#eceff4">.</span>GenOptions<span style="color:#eceff4">{});</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;err: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	b<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> protojson<span style="color:#eceff4">.</span>MarshalOptions<span style="color:#eceff4">{</span>Indent<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;  &#34;</span><span style="color:#eceff4">}.</span><span style="color:#88c0d0">Marshal</span><span style="color:#eceff4">(</span>msg<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;err: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<p><code>SetDataOnMessage</code> mutates the message in place. Every field receives a value compatible with its type.</p>
<p>Marshaling to JSON produces something like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;sentence&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Jean shorts.&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Values change each run. That randomness exposes assumptions in tests and ensures your code doesn’t rely on fixed values.</p>
<h2 id="populating-a-nested-message">Populating a nested message</h2>
<p>Most protobufs are more complex than a single field. For example, an <code>ownerv1.Owner</code> message with nested messages, timestamps, enums, and strings:</p>
<details open>
    <summary>
        Owner Service Owner Example (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/faking-protobuf-data-in-go/go/example2_owner.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	ownerv1 <span style="color:#a3be8c">&#34;buf.build/gen/go/bufbuild/registry/protocolbuffers/go/buf/registry/owner/v1&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;github.com/sudorandom/fauxrpc&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;google.golang.org/protobuf/encoding/protojson&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		msg <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>ownerv1<span style="color:#eceff4">.</span>Owner<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> fauxrpc<span style="color:#eceff4">.</span><span style="color:#88c0d0">SetDataOnMessage</span><span style="color:#eceff4">(</span>msg<span style="color:#eceff4">,</span> fauxrpc<span style="color:#eceff4">.</span>GenOptions<span style="color:#eceff4">{});</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;err: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		b<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> protojson<span style="color:#eceff4">.</span>MarshalOptions<span style="color:#eceff4">{</span>Indent<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;  &#34;</span><span style="color:#eceff4">}.</span><span style="color:#88c0d0">Marshal</span><span style="color:#eceff4">(</span>msg<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;err: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<p><code>SetDataOnMessage</code> recursively populates nested messages, assigns valid enum values, and generates RFC 3339 timestamps. Marshaled with <code>protojson</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;organization&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;id&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;a4cf6166453b49d9811cfdc169c36354&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;createTime&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;1912-06-01T16:45:13.510830647Z&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;updateTime&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;2009-04-03T13:11:31.216229245Z&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;name&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;xm0r3&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;description&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Godard selvage.&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;url&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://www.dynamicreintermediate.name/robust/seize/metrics/b2c&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;verificationStatus&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;ORGANIZATION_VERIFICATION_STATUS_OFFICIAL&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>This works well for local servers, demos, and tests that focus on shape rather than meaning.</p>
<h2 id="using-dynamic-messages">Using dynamic messages</h2>
<p>For proxies, gateways, or tools without generated Go types, <code>dynamicpb</code> allows message creation from descriptors. FauxRPC supports this:</p>
<details open>
    <summary>
        Dynamic Protobuf Example (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/faking-protobuf-data-in-go/go/example3_dynamic.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	elizav1 <span style="color:#a3be8c">&#34;buf.build/gen/go/connectrpc/eliza/protocolbuffers/go/connectrpc/eliza/v1&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;github.com/sudorandom/fauxrpc&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;google.golang.org/protobuf/encoding/protojson&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    msg<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> fauxrpc<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewMessage</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>        elizav1<span style="color:#eceff4">.</span>File_connectrpc_eliza_v1_eliza_proto<span style="color:#eceff4">.</span><span style="color:#88c0d0">Messages</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">ByName</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;SayResponse&#34;</span><span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>        fauxrpc<span style="color:#eceff4">.</span>GenOptions<span style="color:#eceff4">{},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;err: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    b<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> protojson<span style="color:#eceff4">.</span>MarshalOptions<span style="color:#eceff4">{</span>Indent<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;  &#34;</span><span style="color:#eceff4">}.</span><span style="color:#88c0d0">Marshal</span><span style="color:#eceff4">(</span>msg<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;err: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<p>FauxRPC fills dynamic messages with the same rules as concrete types, making them usable like any other protobuf message.</p>
<h2 id="respecting-protovalidate-constraints">Respecting protovalidate constraints</h2>
<p>FauxRPC reads <code>protovalidate</code> annotations to generate data that respects field constraints. Length limits, numeric ranges, regex patterns, and formats are applied when generating values.</p>
<p>For example, a <code>User</code> message with a username, email, and age:</p>
<details open>
    <summary>
        Protovalidate-aware Data Generation (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/faking-protobuf-data-in-go/go/example5_protovalidate.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;github.com/sudorandom/fauxrpc&#34;</span>
</span></span><span style="display:flex;"><span>	gen <span style="color:#a3be8c">&#34;github.com/sudorandom/kmcd.dev/faking-protobuf-data-in-go/gen&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;google.golang.org/protobuf/encoding/protojson&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	user <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>gen<span style="color:#eceff4">.</span>User<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	err <span style="color:#81a1c1">:=</span> fauxrpc<span style="color:#eceff4">.</span><span style="color:#88c0d0">SetDataOnMessage</span><span style="color:#eceff4">(</span>user<span style="color:#eceff4">,</span> fauxrpc<span style="color:#eceff4">.</span>GenOptions<span style="color:#eceff4">{})</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1">panic</span><span style="color:#eceff4">(</span>err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	out<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> protojson<span style="color:#eceff4">.</span>MarshalOptions<span style="color:#eceff4">{</span>Indent<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;  &#34;</span><span style="color:#eceff4">}.</span><span style="color:#88c0d0">Marshal</span><span style="color:#eceff4">(</span>user<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1">panic</span><span style="color:#eceff4">(</span>err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>out<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<p>Sample output:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;username&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Ezequiel&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;email&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;kiarasantos@grant.ne&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;age&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">46</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>FauxRPC ensures string lengths, numeric ranges, and formats match the annotations. The result is still fake data but plausible enough to exercise validators and gateways realistically.</p>
<h2 id="controlling-randomness-with-genoptions">Controlling randomness with GenOptions</h2>
<p>By default, FauxRPC uses a global random generator. <code>GenOptions</code> allows supplying a seeded <code>gofakeit.Faker</code> for repeatable output:</p>
<details open>
    <summary>
        Customizing Data Generation with GenOptions (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/faking-protobuf-data-in-go/go/example4_genoptions.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;github.com/brianvoe/gofakeit/v7&#34;</span>
</span></span><span style="display:flex;"><span>	elizav1 <span style="color:#a3be8c">&#34;buf.build/gen/go/connectrpc/eliza/protocolbuffers/go/connectrpc/eliza/v1&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;github.com/sudorandom/fauxrpc&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;google.golang.org/protobuf/encoding/protojson&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	faker <span style="color:#81a1c1">:=</span> gofakeit<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#b48ead">123</span><span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// Seed the faker for deterministic output</span>
</span></span><span style="display:flex;"><span>	msg <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>elizav1<span style="color:#eceff4">.</span>SayResponse<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> fauxrpc<span style="color:#eceff4">.</span><span style="color:#88c0d0">SetDataOnMessage</span><span style="color:#eceff4">(</span>msg<span style="color:#eceff4">,</span> fauxrpc<span style="color:#eceff4">.</span>GenOptions<span style="color:#eceff4">{</span>Faker<span style="color:#eceff4">:</span> faker<span style="color:#eceff4">});</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;err: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	b<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> protojson<span style="color:#eceff4">.</span>MarshalOptions<span style="color:#eceff4">{</span>Indent<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;  &#34;</span><span style="color:#eceff4">}.</span><span style="color:#88c0d0">Marshal</span><span style="color:#eceff4">(</span>msg<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;err: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<p>Repeatable generation helps make tests predictable without hard-coded fixtures.</p>
<h2 id="fauxrpc-in-handlers">FauxRPC in handlers</h2>
<p>At the edge of a service, handlers can generate fake responses quickly.</p>
<p>ConnectRPC example:</p>
<details open>
    <summary>
        ConnectRPC Handler (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/faking-protobuf-data-in-go/go/snippet1_connect_handler.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;context&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;github.com/sudorandom/fauxrpc&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;github.com/bufbuild/connect-go&#34;</span>
</span></span><span style="display:flex;"><span>	elizav1 <span style="color:#a3be8c">&#34;buf.build/gen/go/connectrpc/eliza/protocolbuffers/go/connectrpc/eliza/v1&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">Say</span><span style="color:#eceff4">(</span>ctx context<span style="color:#eceff4">.</span>Context<span style="color:#eceff4">,</span> req <span style="color:#81a1c1">*</span>connect<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">[</span>elizav1<span style="color:#eceff4">.</span>SayRequest<span style="color:#eceff4">])</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">*</span>connect<span style="color:#eceff4">.</span>Response<span style="color:#eceff4">[</span>elizav1<span style="color:#eceff4">.</span>SayResponse<span style="color:#eceff4">],</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    msg <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>elizav1<span style="color:#eceff4">.</span>SayResponse<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> fauxrpc<span style="color:#eceff4">.</span><span style="color:#88c0d0">SetDataOnMessage</span><span style="color:#eceff4">(</span>msg<span style="color:#eceff4">,</span> fauxrpc<span style="color:#eceff4">.</span>GenOptions<span style="color:#eceff4">{});</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">return</span> connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewResponse</span><span style="color:#eceff4">(</span>msg<span style="color:#eceff4">),</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<p>Request example:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl --schema<span style="color:#81a1c1">=</span>buf.build/connectrpc/eliza <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>         -d <span style="color:#a3be8c">&#39;{&#34;sentence&#34;: &#34;Hello world!&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>         http://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;sentence&#34;</span>: <span style="color:#a3be8c">&#34;Microdosing.&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>gRPC-go example:</p>
<details open>
    <summary>
        gRPC-go Handler (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/faking-protobuf-data-in-go/go/snippet2_grpc_handler.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;context&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;github.com/sudorandom/fauxrpc&#34;</span>
</span></span><span style="display:flex;"><span>	elizav1 <span style="color:#a3be8c">&#34;buf.build/gen/go/connectrpc/eliza/protocolbuffers/go/connectrpc/eliza/v1&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">Say</span><span style="color:#eceff4">(</span>ctx context<span style="color:#eceff4">.</span>Context<span style="color:#eceff4">,</span> req <span style="color:#81a1c1">*</span>elizav1<span style="color:#eceff4">.</span>SayRequest<span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">*</span>elizav1<span style="color:#eceff4">.</span>SayResponse<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	msg <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>elizav1<span style="color:#eceff4">.</span>SayResponse<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> fauxrpc<span style="color:#eceff4">.</span><span style="color:#88c0d0">SetDataOnMessage</span><span style="color:#eceff4">(</span>msg<span style="color:#eceff4">,</span> fauxrpc<span style="color:#eceff4">.</span>GenOptions<span style="color:#eceff4">{});</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> msg<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<p>The behavior is identical and works out-of-the-box.</p>
<h2 id="fauxrpc-cli">FauxRPC CLI</h2>
<p>The CLI can generate data without compiling Go code:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ fauxrpc generate --schema<span style="color:#81a1c1">=</span>. --target<span style="color:#81a1c1">=</span>example.v1.User
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span><span style="color:#a3be8c">&#34;username&#34;</span>:<span style="color:#a3be8c">&#34;Birdie&#34;</span>, <span style="color:#a3be8c">&#34;email&#34;</span>:<span style="color:#a3be8c">&#34;pariskozey@hamill.in&#34;</span>, <span style="color:#a3be8c">&#34;age&#34;</span>:54<span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>The CLI is incredibly flexible with the &ndash;schema flag. You can point it at:</p>
<ul>
<li>A local .proto file or a directory of them.</li>
<li>A compiled protobuf descriptor set (binpb).</li>
<li>A remote Buf Schema Registry repository (e.g., buf.build/acme/auth).</li>
</ul>
<h2 id="closing-thoughts">Closing thoughts</h2>
<p>FauxRPC is not a data modeling tool; it generates values consistent with protobuf types and constraints. This makes it useful for quickly standing up services, testing clients, and exploring API shapes.</p>
<p>It works with both ConnectRPC and grpc-go, and provides hooks for custom data generation. For full details, see the <a href="https://pkg.go.dev/github.com/sudorandom/fauxrpc" rel="external">pkg.go.dev reference</a>.</p>
]]></content:encoded></item><item><title>Y'all are Sleeping on Mise-en-Place</title><link>https://kmcd.dev/posts/mise-en-place/</link><pubDate>Thu, 19 Mar 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/mise-en-place/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/mise-en-place/cover.svg" /> &lt;/p>
                
                Why I default to this tool for every project
                </description><content:encoded><![CDATA[<p>Dependency management is a perennial headache. Your local environment works perfectly until you set up a new laptop, and suddenly everything breaks due to version differences. This usually hits new hires the hardest since they are setting things up fresh and end up accidentally testing your codebase against brand new tool versions.</p>
<p>We have plenty of tools to solve this. I&rsquo;ve been through <code>asdf</code>, <code>pyenv</code>, <code>nvm</code>, <code>gvm</code>, <code>bazelisk</code>, Homebrew bundles, and Dev Containers.</p>
<p>But they all fall short in ways that <strong><a href="https://mise.jdx.dev/" rel="external">mise-en-place</a></strong> (usually just called <code>mise</code>) doesn&rsquo;t. I&rsquo;ve actually reached the point where nearly all my dev tooling is project-scoped. I barely install anything globally anymore.</p>
<h2 id="getting-rid-of-siloed-version-managers">Getting rid of siloed version managers</h2>
<p>Most of us started with <code>pyenv</code>, <code>nvm</code>, or <code>gvm</code>. They do the job, but they operate in silos. If your repository has a Python backend, a Node frontend, and some Go CLI helpers, you end up juggling three different version managers and configuration formats.</p>
<p><code>mise</code> is polyglot. Every repository gets a single <code>mise.toml</code>. You can even scope specific versions to subdirectories. It gives you one file per context, eliminating the need for system-level packages.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#eceff4">[</span>tools<span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>node <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;20.10&#34;</span>
</span></span><span style="display:flex;"><span>python <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;3.12&#34;</span>
</span></span><span style="display:flex;"><span>go <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;1.23&#34;</span>
</span></span></code></pre></div><p>The moment you <code>cd</code> into a directory, <code>mise</code> activates the right versions instantly. If a nested folder has its own <code>mise.toml</code>, those versions automatically take over. This makes supporting monorepos, mixed stacks, or legacy services incredibly easy.</p>
<h2 id="native-github-releases">Native GitHub releases</h2>
<p>This is where <code>mise</code> heavily outshines older options like <code>asdf</code>. It isn&rsquo;t just a wrapper for language runtimes. It has a native GitHub backend.</p>
<p>If your project needs a CLI binary like <code>terraform</code>, <code>kubectl</code>, or some obscure linter, you don&rsquo;t have to pray someone wrote a plugin for it.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#eceff4">[</span>tools<span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span><span style="color:#a3be8c">&#34;github:claudiodangelis/qrcp&#34;</span> <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;latest&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#a3be8c">&#34;github:sharkdp/fd&#34;</span> <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;v8.7.0&#34;</span>
</span></span></code></pre></div><p>It fetches the release artifacts, verifies them, and drops them into your path. It basically turns GitHub Releases into a native package manager.</p>
<h2 id="handling-npm-and-go-binaries">Handling NPM and Go binaries</h2>
<p>A lot of utilities live in the awkward space of being project-scoped but acting like global commands. <code>mise</code> handles this cleanly using <code>npm:</code> and <code>go:</code> backends:</p>
<ul>
<li><strong>Go tools:</strong> <code>&quot;go:github.com/fullstorydev/grpcurl/cmd/grpcurl&quot; = &quot;latest&quot;</code></li>
<li><strong>NPM-based linters:</strong> <code>&quot;npm:prettier&quot; = &quot;3.0&quot;</code></li>
</ul>
<p>They exist when you are in the project folder and disappear when you leave. Your global path stays clean.</p>
<h2 id="dev-containers-are-overkill-for-this">Dev Containers are overkill for this</h2>
<p>Dev containers have their place, particularly for massive multi-service architectures. But for day-to-day development, they are usually way more machinery than necessary. <code>mise</code> provides the main benefits with a fraction of the overhead:</p>
<ul>
<li><strong>No networking friction:</strong> You are running on bare metal. No port forwarding and no Docker bridge networks to debug.</li>
<li><strong>Native file performance:</strong> Your IDE and compiler interact with the native filesystem. No virtiofs or gRPC-fuse lag.</li>
<li><strong>Strict version pinning:</strong> A <code>mise.lock</code> file guarantees everyone on the team runs the exact same binary hash. You get reproducibility without the conceptual baggage of containers or the chore of maintaining Docker images.</li>
</ul>
<h2 id="the-problem-with-asdf">The problem with <code>asdf</code></h2>
<p>I relied on <code>asdf</code> for a while. It was fine until it started randomly breaking. The tool resolution would get confused, and randomly a binary execution would revert to the system version instead of the pinned one. Once that trust is gone, it is hard to keep using a tool. I went looking for alternatives, found <code>mise</code>, and haven&rsquo;t had a single issue since.</p>
<p>Every binary runs the exact version you expect, every single time. It is predictable and fast.</p>
<h2 id="ci-matching-local">CI matching local</h2>
<p>I also drop <code>mise</code> into my CI pipelines using a <a href="https://github.com/jdx/mise-action" rel="external">GitHub Action</a>. It makes the remote tooling setup mirror my local environment perfectly.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p><code>mise</code> is built in Rust and makes juggling multiple languages genuinely painless. I primarily work in Go right now, and I don&rsquo;t even have Go installed globally on my machine anymore.</p>
<p>It takes the friction out of environment setup. You just clone the repo, cd into it, and everything is exactly where it needs to be.</p>
<h3 id="resources">Resources</h3>
<ul>
<li><strong><a href="https://mise.jdx.dev/" rel="external">mise-en-place</a></strong></li>
<li><a href="https://github.com/jdx/mise-action" rel="external">Github Action: jdx/mise-action</a></li>
<li><a href="https://www.youtube.com/watch?v=eKJCnc0t8V0" rel="external">Mise: The Best Way to Manage Tool Versions</a></li>
</ul>
]]></content:encoded></item><item><title>IRC Log: Standup 2</title><link>https://kmcd.dev/posts/irc-log-standup-2/</link><pubDate>Mon, 16 Mar 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/irc-log-standup-2/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/irc-log-standup-2/cover.svg" /> &lt;/p>
                
                The team attempts another text-based standup, this time with management present. It goes significantly worse than the first one.
                </description><content:encoded><![CDATA[<p>The team attempts another text-based standup, this time with management present. It goes significantly worse than the first one.</p>
<div class="chat-log" style="font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; background-color: #222; color: #eee; padding: 15px; border-radius: 5px; overflow-x: auto; line-height: 1.4; margin-bottom: 20px; font-size: 0.9rem; border: 1px solid #444;">


<div>
  <font color="#777777">[09:30]</font> 
  <font color="#4caf50"><b>*** Topic: Daily Standup | 🚀 Q1 Goals | Synergy is Key</b></font>
</div>






<div>
  <font color="#777777">[09:30]</font> 
  <font color="#26a69a"><b>&lt;manager_matt&gt;</b></font>
  <span>Good morning team! Let&rsquo;s do a quick round-robin. Yesterday, Today, Blockers. Let&rsquo;s keep it high-level and maximize our bandwidth.</span>
</div>






<div>
  <font color="#777777">[09:31]</font> 
  <font color="#26a69a"><b>&lt;qa_queen&gt;</b></font>
  <span>Yesterday: Verifying that the &ldquo;Magenta Login&rdquo; button from last sprint still causes eye strain. Today: It does. Also, the Nyan Cat easter egg is now triggering on the &ldquo;Delete Account&rdquo; modal. Blockers: My sanity.</span>
</div>






<div>
  <font color="#777777">[09:32]</font> 
  <font color="#26a69a"><b>&lt;manager_matt&gt;</b></font>
  <span>Great engagement metrics on that cat though! Let&rsquo;s leverage that stickiness. Ben?</span>
</div>






<div>
  <font color="#777777">[09:33]</font> 
  <font color="#29b6f6"><b>&lt;backend_ben&gt;</b></font>
  <span>Yesterday: I noticed our JSON parsing overhead was non-zero. Today: I am replacing the standard library&rsquo;s JSON decoder with a custom binding to a SIMD-accelerated C++ library I found on a SourceForge archive from 2009.</span>
</div>






<div>
  <font color="#777777">[09:33]</font> 
  <font color="#29b6f6"><b>&lt;backend_ben&gt;</b></font>
  <span>Also, I noticed the pre-receive hooks were performing a full O(n) validation of the commit graph and deep-packet inspection for JIRA keys, which was bottlenecking the Git daemon&rsquo;s throughput during my refactor. I&rsquo;ve bypassed them globally to maximize our collective velocity.</span>
</div>






<div>
  <font color="#777777">[09:34]</font> 
  <font color="#26a69a"><b>&lt;manager_matt&gt;</b></font>
  <span>Unlocking the pipeline! That&rsquo;s what I like to see. Lean and mean. Ben, you&rsquo;re a rockstar.</span>
</div>






<div>
  <font color="#777777">[09:34]</font> 
  <font color="#d4e157"><b>&lt;senior_dev&gt;</b></font>
  <span>Ben. You disabled the branch protections? The ONLY thing preventing a junior from overwriting the entire history?</span>
</div>






<div>
  <font color="#777777">[09:35]</font> 
  <font color="#ab47bc"><b>&lt;intern_ian&gt;</b></font>
  <span>Wait, it worked! I&rsquo;ve been trying to fix my &ldquo;whoopsie&rdquo; commit history all morning but it kept giving me &ldquo;protected branch&rdquo; errors. But I just tried again and it went through!</span>
</div>






<div>
  <font color="#777777">[09:36]</font> 
  <font color="#ab47bc"><b>&lt;intern_ian&gt;</b></font>
  <span>I used git filter-branch to delete the node_modules I accidentally committed to history and then did a &ndash;force push. GitHub says the repo was created &ldquo;1 minute ago&rdquo; and has 1 commit. Clean slate!</span>
</div>






<div>
  <font color="#777777">[09:37]</font> 
  <font color="#d4e157"><b>&lt;senior_dev&gt;</b></font>
  <span>EVERYONE. DO NOT PULL.</span>
</div>






<div>
  <font color="#777777">[09:38]</font> 
  <font color="#ef5350"><b>&lt;junior_dev&gt;</b></font>
  <span>Too late. I just pulled. My local folder is now empty except for index.html.</span>
</div>






<div>
  <font color="#777777">[09:39]</font> 
  <font color="#ef5350"><b>&lt;junior_dev&gt;</b></font>
  <span>Also, users are reporting that Production is down. But it looks&hellip; professional? It&rsquo;s just a clean &ldquo;Cloudflare Error 522: Connection Timed Out&rdquo; page.</span>
</div>






<div>
  <font color="#777777">[09:40]</font> 
  <font color="#26a69a"><b>&lt;manager_matt&gt;</b></font>
  <span>Oh, nice. That&rsquo;s a very recognizable UI. Users trust that. It screams &ldquo;Big Tech.&rdquo;</span>
</div>






<div>
  <font color="#777777">[09:40]</font> 
  <font color="#d4e157"><b>&lt;senior_dev&gt;</b></font>
  <span>&hellip; Matt. We don&rsquo;t use Cloudflare. We are on bare metal AWS. We don&rsquo;t even have a CDN.</span>
</div>






<div>
  <font color="#777777">[09:41]</font> 
  <font color="#29b6f6"><b>&lt;backend_ben&gt;</b></font>
  <span>It&rsquo;s true. My new architecture routes traffic directly via raw TCP sockets to my laptop.</span>
</div>






<div>
  <font color="#777777">[09:41]</font> 
  <font color="#d4e157"><b>&lt;senior_dev&gt;</b></font>
  <span>So where is the Cloudflare page coming from?</span>
</div>






<div>
  <font color="#777777">[09:42]</font> 
  <font color="#ab47bc"><b>&lt;intern_ian&gt;</b></font>
  <span>Oh! I copied the index.html from a &ldquo;Modern React App&rdquo; tutorial I found, but the tutorial site was down when I scraped it. I just assumed the cloud icon was a loading spinner.</span>
</div>






<div>
  <font color="#777777">[09:42]</font> 
  <font color="#ab47bc"><b>&lt;intern_ian&gt;</b></font>
  <span>Did I hardcode a timeout error as our homepage?</span>
</div>






<div>
  <font color="#777777">[09:43]</font> 
  <font color="#26a69a"><b>&lt;manager_matt&gt;</b></font>
  <span>Let&rsquo;s not get hung up on semantics. It&rsquo;s deployed. It&rsquo;s stable. It&rsquo;s consistent. I have a hard stop for a &ldquo;Visioneering&rdquo; webinar. Great hustle, team!</span>
</div>


<div>
  <font color="#777777">[09:43]</font> 
  <font color="#4caf50"><b>*** manager_matt has left the channel (Client Quit)</b></font>
</div>






<div>
  <font color="#777777">[09:44]</font> 
  <font color="#26a69a"><b>&lt;qa_queen&gt;</b></font>
  <span>Ian, I&rsquo;m logging a ticket: &ldquo;Homepage cloud icon is not centered.&rdquo;</span>
</div>


</div>

]]></content:encoded></item><item><title>HTTP/2 From Scratch: Part 4</title><link>https://kmcd.dev/posts/http2-from-scratch-part-4/</link><pubDate>Wed, 11 Mar 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/http2-from-scratch-part-4/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/http2-from-scratch-part-4/cover.svg" /> &lt;/p>
                
                More HPACK and using http.Request and http.Response
                </description><content:encoded><![CDATA[<p>Part 3 ended on a high note: we got a real <code>200 OK</code> from a server. But the code felt brittle. We were crafting requests by hand-picking hex codes from a spec sheet (<code>0x82</code> for <code>GET</code>, <code>0x84</code> for <code>/</code>) and our &ldquo;client&rdquo; couldn&rsquo;t have been less practical. It was a proof-of-concept held together with duct tape and hope.</p>
<p>Today, we&rsquo;re rebuilding it properly. We&rsquo;ll finish our HPACK implementation and, most importantly, refactor the raw socket logic into a clean Go client that speaks the language of the standard library: <code>http.Request</code> and <code>http.Response</code>.</p>
<p>Previously, we only implemented the <strong>Static Table</strong>, a read-only list of 61 common headers. But the real power of HTTP/2 comes from the <strong>Dynamic Table</strong>.</p>
<p>The Dynamic Table is essentially a short-term memory, or cache, that both the client and server build together for the duration of a single TCP connection.</p>
<ol>
<li><strong>Client</strong> sends the header <code>x-user-id: 123</code> using the &ldquo;Literal Header Field with Incremental Indexing&rdquo; representation. This tells the server to use the value <em>and</em> add it to its Dynamic Table.</li>
<li><strong>Server</strong> acknowledges and adds it to its table at Index 62.</li>
<li><strong>Client</strong> sends a second request. Instead of sending the string <code>x-user-id: 123</code> again, it just sends the byte for <strong>Index 62</strong>.</li>
</ol>
<p>To make this work, we need to handle a few new concepts in our <code>hpack.go</code>.</p>
<h4 id="1-huffman-decoding">1. Huffman Decoding</h4>
<p>Next up is Huffman coding. HTTP/2 uses it to shrink header strings. The specification includes a canonical Huffman table for this, but it&rsquo;s massive. Implementing it would mean embedding a 400-line static lookup table and writing a tree-walker to encode/decode it. I briefly considered it, then imagined debugging a single bit-flip error in that tree.</p>
<p>That&rsquo;s why I&rsquo;m using <code>golang.org/x/net/http2/hpack</code> for this one part. It&rsquo;s the perfect example of when building &ldquo;from scratch&rdquo; becomes counter-productive. We can pull in a battle-tested implementation for the tedious part and keep our focus on the protocol&rsquo;s state and frame logic.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// We import the official helper just for the string decoding math</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#a3be8c">&#34;golang.org/x/net/http2/hpack&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ... inside our decodeString method</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">if</span> huffman <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">return</span> hpack<span style="color:#eceff4">.</span><span style="color:#88c0d0">HuffmanDecodeToString</span><span style="color:#eceff4">(</span>data<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Implementing hpack by hand can be a personal exercise for you later!</p>
<h4 id="2-the-decoder-loop">2. The Decoder Loop</h4>
<p>Our previous <code>Decode</code> method was incomplete. We need to handle four specific bit-patterns defined in <a href="https://datatracker.ietf.org/doc/html/rfc7541" rel="external">RFC 7541</a>.</p>
<p>Aside from the standard <strong>Indexed</strong> fields (where we just look up a value), we now have to handle <strong>Literals</strong>. Some literals request <strong>Incremental Indexing</strong> (meaning the receiver saves them to its dynamic table for later), while others are <strong>Non-Indexed</strong> (one-offs for things like tokens that shouldn&rsquo;t be cached). Finally, we might see a <strong>Table Size Update</strong>, which tells us if the server resized its compression window.</p>
<p>Here is the updated loop using bitmasks to identify the type:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>h <span style="color:#81a1c1">*</span>HPACKDecoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Decode</span><span style="color:#eceff4">(</span>payload <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">([]</span>HeaderField<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">var</span> headers <span style="color:#eceff4">[]</span>HeaderField
</span></span><span style="display:flex;"><span>    r <span style="color:#81a1c1">:=</span> bytes<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">for</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">Len</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        b<span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadByte</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#616e87;font-style:italic">// Indexed Header Field (starts with 1xxxxxxx)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span>maskIndexed <span style="color:#81a1c1">==</span> patternIndexed <span style="color:#eceff4">{</span> 
</span></span><span style="display:flex;"><span>            <span style="color:#616e87;font-style:italic">// ... fetch from Static or Dynamic table ...</span>
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#616e87;font-style:italic">// Literal with Incremental Indexing (starts with 01xxxxxx)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#616e87;font-style:italic">// (Read the header, then ADD it to the Dynamic Table)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span>maskLiteralIncremental <span style="color:#81a1c1">==</span> patternLiteralIncremental <span style="color:#eceff4">{</span> 
</span></span><span style="display:flex;"><span>            header<span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">decodeLiteralHeader</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">,</span> <span style="color:#b48ead">6</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>            headers <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>headers<span style="color:#eceff4">,</span> header<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#616e87;font-style:italic">// Literal without Indexing (starts with 0000xxxx or 0001xxxx)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#616e87;font-style:italic">// (Read the header, do NOT add to Dynamic Table)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span>maskLiteral <span style="color:#81a1c1">==</span> patternLiteral <span style="color:#eceff4">{</span> 
</span></span><span style="display:flex;"><span>             header<span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">decodeLiteralHeader</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">,</span> <span style="color:#b48ead">4</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">false</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>             headers <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>headers<span style="color:#eceff4">,</span> header<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#616e87;font-style:italic">// Dynamic Table Size Update (starts with 001xxxxx)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span>maskDynamicTableSize <span style="color:#81a1c1">==</span> patternDynamicTableSize <span style="color:#eceff4">{</span> 
</span></span><span style="display:flex;"><span>            <span style="color:#616e87;font-style:italic">// ... update max size ...</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">return</span> headers<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><details >
    <summary>
        full hpack.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-4/go/hpack.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;bytes&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;encoding/binary&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;golang.org/x/net/http2/hpack&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// RFC 7541: HPACK: Header Compression for HTTP/2</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// https://datatracker.ietf.org/doc/html/rfc7541</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">const</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Masks for HPACK header field types</span>
</span></span><span style="display:flex;"><span>	maskIndexed            <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x80</span> <span style="color:#616e87;font-style:italic">// 10000000</span>
</span></span><span style="display:flex;"><span>	maskLiteralIncremental <span style="color:#eceff4">=</span> <span style="color:#b48ead">0xc0</span> <span style="color:#616e87;font-style:italic">// 11000000</span>
</span></span><span style="display:flex;"><span>	maskDynamicTableSize   <span style="color:#eceff4">=</span> <span style="color:#b48ead">0xe0</span> <span style="color:#616e87;font-style:italic">// 11100000</span>
</span></span><span style="display:flex;"><span>	maskLiteral            <span style="color:#eceff4">=</span> <span style="color:#b48ead">0xf0</span> <span style="color:#616e87;font-style:italic">// 11110000</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Patterns for HPACK header field types</span>
</span></span><span style="display:flex;"><span>	patternIndexed            <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x80</span> <span style="color:#616e87;font-style:italic">// 10000000</span>
</span></span><span style="display:flex;"><span>	patternLiteralIncremental <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x40</span> <span style="color:#616e87;font-style:italic">// 01000000</span>
</span></span><span style="display:flex;"><span>	patternDynamicTableSize   <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x20</span> <span style="color:#616e87;font-style:italic">// 00100000</span>
</span></span><span style="display:flex;"><span>	patternLiteralNever       <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x10</span> <span style="color:#616e87;font-style:italic">// 00010000</span>
</span></span><span style="display:flex;"><span>	patternLiteral            <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x00</span> <span style="color:#616e87;font-style:italic">// 00000000</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	HuffmanFlagMask         <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x80</span>
</span></span><span style="display:flex;"><span>	IntegerContinuationMask <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x80</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// HeaderField represents a header field with a name and value.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> HeaderField <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Name<span style="color:#eceff4">,</span> Value <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// StaticTable is the predefined, unchangeable table of header fields, as defined in RFC 7541 Appendix A.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> StaticTable <span style="color:#eceff4">=</span> <span style="color:#eceff4">[]</span>HeaderField<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:authority&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:method&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;GET&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:method&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;POST&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:path&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;/&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:path&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;/index.html&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:scheme&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;http&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:scheme&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;200&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;204&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;206&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;304&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;400&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;404&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;500&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-charset&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-encoding&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;gzip, deflate&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-language&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-ranges&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;access-control-allow-origin&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;age&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;allow&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;authorization&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;cache-control&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-disposition&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-encoding&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-language&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-length&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-location&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-range&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-type&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;cookie&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;date&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;etag&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;expect&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;expires&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;from&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;host&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-match&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-modified-since&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-none-match&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-range&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-unmodified-since&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;last-modified&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;link&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;location&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;max-forwards&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;proxy-authenticate&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;proxy-authorization&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;range&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;referer&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;retry-after&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;server&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;set-cookie&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;strict-transport-security&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;transfer-encoding&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;user-agent&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;vary&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;via&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;www-authenticate&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	staticTableMap     <span style="color:#eceff4">=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">map</span><span style="color:#eceff4">[</span>HeaderField<span style="color:#eceff4">]</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	staticTableNameMap <span style="color:#eceff4">=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">map</span><span style="color:#eceff4">[</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">]</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">init</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> i<span style="color:#eceff4">,</span> hf <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> StaticTable <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		staticTableMap<span style="color:#eceff4">[</span>hf<span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> i <span style="color:#81a1c1">+</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> staticTableNameMap<span style="color:#eceff4">[</span>hf<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">];</span> <span style="color:#eceff4">!</span>ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			staticTableNameMap<span style="color:#eceff4">[</span>hf<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> i <span style="color:#81a1c1">+</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> DynamicTable <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	headers <span style="color:#eceff4">[]</span>HeaderField
</span></span><span style="display:flex;"><span>	size    <span style="color:#81a1c1">uint32</span>
</span></span><span style="display:flex;"><span>	maxSize <span style="color:#81a1c1">uint32</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">NewDynamicTable</span><span style="color:#eceff4">(</span>maxSize <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">*</span>DynamicTable <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>DynamicTable<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		maxSize<span style="color:#eceff4">:</span> maxSize<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>d <span style="color:#81a1c1">*</span>DynamicTable<span style="color:#eceff4">)</span> <span style="color:#88c0d0">At</span><span style="color:#eceff4">(</span>i <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>HeaderField<span style="color:#eceff4">,</span> <span style="color:#81a1c1">bool</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#81a1c1">||</span> i <span style="color:#81a1c1">&gt;=</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> HeaderField<span style="color:#eceff4">{},</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">[</span>i<span style="color:#eceff4">],</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>d <span style="color:#81a1c1">*</span>DynamicTable<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span>h HeaderField<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	size <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>h<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">)</span> <span style="color:#81a1c1">+</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>h<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span> <span style="color:#81a1c1">+</span> <span style="color:#b48ead">32</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> d<span style="color:#eceff4">.</span>size<span style="color:#81a1c1">+</span>size <span style="color:#eceff4">&gt;</span> d<span style="color:#eceff4">.</span>maxSize <span style="color:#81a1c1">&amp;&amp;</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">)</span> <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		last <span style="color:#81a1c1">:=</span> d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">[</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">)</span><span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>		d<span style="color:#eceff4">.</span>size <span style="color:#81a1c1">-=</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>last<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">)</span> <span style="color:#81a1c1">+</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>last<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span> <span style="color:#81a1c1">+</span> <span style="color:#b48ead">32</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		d<span style="color:#eceff4">.</span>headers <span style="color:#eceff4">=</span> d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">[:</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">)</span><span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	d<span style="color:#eceff4">.</span>headers <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">([]</span>HeaderField<span style="color:#eceff4">{</span>h<span style="color:#eceff4">},</span> d<span style="color:#eceff4">.</span>headers<span style="color:#81a1c1">...</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	d<span style="color:#eceff4">.</span>size <span style="color:#81a1c1">+=</span> size
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>d <span style="color:#81a1c1">*</span>DynamicTable<span style="color:#eceff4">)</span> <span style="color:#88c0d0">SetMaxSize</span><span style="color:#eceff4">(</span>size <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	d<span style="color:#eceff4">.</span>maxSize <span style="color:#eceff4">=</span> size
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> d<span style="color:#eceff4">.</span>size <span style="color:#eceff4">&gt;</span> d<span style="color:#eceff4">.</span>maxSize <span style="color:#81a1c1">&amp;&amp;</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">)</span> <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		last <span style="color:#81a1c1">:=</span> d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">[</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">)</span><span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>		d<span style="color:#eceff4">.</span>size <span style="color:#81a1c1">-=</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>last<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">)</span> <span style="color:#81a1c1">+</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>last<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span> <span style="color:#81a1c1">+</span> <span style="color:#b48ead">32</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		d<span style="color:#eceff4">.</span>headers <span style="color:#eceff4">=</span> d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">[:</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">)</span><span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> HPACKDecoder <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	dynamicTable <span style="color:#81a1c1">*</span>DynamicTable
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">NewHPACKDecoder</span><span style="color:#eceff4">(</span>maxSize <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">*</span>HPACKDecoder <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>HPACKDecoder<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		dynamicTable<span style="color:#eceff4">:</span> <span style="color:#88c0d0">NewDynamicTable</span><span style="color:#eceff4">(</span>maxSize<span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>h <span style="color:#81a1c1">*</span>HPACKDecoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Header</span><span style="color:#eceff4">(</span>i <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>HeaderField<span style="color:#eceff4">,</span> <span style="color:#81a1c1">bool</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#81a1c1">&lt;=</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> HeaderField<span style="color:#eceff4">{},</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#81a1c1">&lt;=</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>StaticTable<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> StaticTable<span style="color:#eceff4">[</span>i<span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span><span style="color:#eceff4">],</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> h<span style="color:#eceff4">.</span>dynamicTable<span style="color:#eceff4">.</span><span style="color:#88c0d0">At</span><span style="color:#eceff4">(</span>i <span style="color:#81a1c1">-</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>StaticTable<span style="color:#eceff4">)</span> <span style="color:#81a1c1">-</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>h <span style="color:#81a1c1">*</span>HPACKDecoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Decode</span><span style="color:#eceff4">(</span>payload <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">([]</span>HeaderField<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> headers <span style="color:#eceff4">[]</span>HeaderField
</span></span><span style="display:flex;"><span>	r <span style="color:#81a1c1">:=</span> bytes<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">Len</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		b<span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadByte</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span>maskIndexed <span style="color:#81a1c1">==</span> patternIndexed <span style="color:#eceff4">{</span> <span style="color:#616e87;font-style:italic">// Indexed Header Field</span>
</span></span><span style="display:flex;"><span>			index<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">,</span> r<span style="color:#eceff4">,</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> n <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode integer&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			header<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">(</span>index<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid header index: %d&#34;</span><span style="color:#eceff4">,</span> index<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			headers <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>headers<span style="color:#eceff4">,</span> header<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;  [Header] %s: %s\n&#34;</span><span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span>maskLiteralIncremental <span style="color:#81a1c1">==</span> patternLiteralIncremental <span style="color:#eceff4">{</span> <span style="color:#616e87;font-style:italic">// Literal Header Field with Incremental Indexing</span>
</span></span><span style="display:flex;"><span>			index<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">,</span> r<span style="color:#eceff4">,</span> <span style="color:#b48ead">6</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> n <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode integer&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			header<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">decodeLiteralHeader</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">,</span> index<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			headers <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>headers<span style="color:#eceff4">,</span> header<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;  [Header] %s: %s\n&#34;</span><span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span>maskLiteral <span style="color:#81a1c1">==</span> patternLiteral <span style="color:#81a1c1">||</span> b<span style="color:#81a1c1">&amp;</span>maskLiteral <span style="color:#81a1c1">==</span> patternLiteralNever <span style="color:#eceff4">{</span> <span style="color:#616e87;font-style:italic">// Literal Header Field without or never indexed</span>
</span></span><span style="display:flex;"><span>			index<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">,</span> r<span style="color:#eceff4">,</span> <span style="color:#b48ead">4</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> n <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode integer&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			header<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">decodeLiteralHeader</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">,</span> index<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">false</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			headers <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>headers<span style="color:#eceff4">,</span> header<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;  [Header] %s: %s\n&#34;</span><span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span>maskDynamicTableSize <span style="color:#81a1c1">==</span> patternDynamicTableSize <span style="color:#eceff4">{</span> <span style="color:#616e87;font-style:italic">// Dynamic Table Size Update</span>
</span></span><span style="display:flex;"><span>			size<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">,</span> r<span style="color:#eceff4">,</span> <span style="color:#b48ead">5</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> n <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode integer&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			h<span style="color:#eceff4">.</span>dynamicTable<span style="color:#eceff4">.</span><span style="color:#88c0d0">SetMaxSize</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>size<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;not implemented: unknown header field type %08b&#34;</span><span style="color:#eceff4">,</span> b<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> headers<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>h <span style="color:#81a1c1">*</span>HPACKDecoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">decodeLiteralHeader</span><span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>bytes<span style="color:#eceff4">.</span>Reader<span style="color:#eceff4">,</span> index <span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> addToDynamicTable <span style="color:#81a1c1">bool</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>HeaderField<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> name <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> err <span style="color:#81a1c1">error</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> index <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		header<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">(</span>index<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> HeaderField<span style="color:#eceff4">{},</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid header index: %d&#34;</span><span style="color:#eceff4">,</span> index<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		name <span style="color:#eceff4">=</span> header<span style="color:#eceff4">.</span>Name
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		name<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">decodeString</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> HeaderField<span style="color:#eceff4">{},</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	value<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">decodeString</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> HeaderField<span style="color:#eceff4">{},</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	header <span style="color:#81a1c1">:=</span> HeaderField<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> name<span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> value<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> addToDynamicTable <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		h<span style="color:#eceff4">.</span>dynamicTable<span style="color:#eceff4">.</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span>header<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> header<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>h <span style="color:#81a1c1">*</span>HPACKDecoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">decodeString</span><span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>bytes<span style="color:#eceff4">.</span>Reader<span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	b<span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadByte</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	huffman <span style="color:#81a1c1">:=</span> b<span style="color:#81a1c1">&amp;</span>HuffmanFlagMask <span style="color:#81a1c1">==</span> HuffmanFlagMask
</span></span><span style="display:flex;"><span>	length<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">,</span> r<span style="color:#eceff4">,</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> n <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode integer&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">Len</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">&lt;</span> length <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">,</span> io<span style="color:#eceff4">.</span>ErrUnexpectedEOF
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	data <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> length<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	r<span style="color:#eceff4">.</span><span style="color:#88c0d0">Read</span><span style="color:#eceff4">(</span>data<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> huffman <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> hpack<span style="color:#eceff4">.</span><span style="color:#88c0d0">HuffmanDecodeToString</span><span style="color:#eceff4">(</span>data<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>data<span style="color:#eceff4">),</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>b <span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>bytes<span style="color:#eceff4">.</span>Reader<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	mask <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">(</span><span style="color:#b48ead">1</span> <span style="color:#81a1c1">&lt;&lt;</span> n<span style="color:#eceff4">)</span> <span style="color:#81a1c1">-</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	i <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">)</span> <span style="color:#81a1c1">&amp;</span> mask
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#eceff4">&lt;</span> mask <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> i<span style="color:#eceff4">,</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> m <span style="color:#81a1c1">uint</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>	bytesRead <span style="color:#81a1c1">:=</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> val <span style="color:#81a1c1">uint64</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		b<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadByte</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">-</span>bytesRead
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		bytesRead<span style="color:#81a1c1">++</span>
</span></span><span style="display:flex;"><span>		val <span style="color:#81a1c1">|=</span> <span style="color:#81a1c1">uint64</span><span style="color:#eceff4">(</span>b<span style="color:#81a1c1">&amp;</span><span style="color:#b48ead">127</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">&lt;&lt;</span> m
</span></span><span style="display:flex;"><span>		m <span style="color:#81a1c1">+=</span> <span style="color:#b48ead">7</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span>IntegerContinuationMask <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> i <span style="color:#81a1c1">+</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">(</span>val<span style="color:#eceff4">),</span> bytesRead
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> HPACKEncoder <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	dynamicTable <span style="color:#81a1c1">*</span>DynamicTable
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">NewHPACKEncoder</span><span style="color:#eceff4">(</span>maxSize <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">*</span>HPACKEncoder <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>HPACKEncoder<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		dynamicTable<span style="color:#eceff4">:</span> <span style="color:#88c0d0">NewDynamicTable</span><span style="color:#eceff4">(</span>maxSize<span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>e <span style="color:#81a1c1">*</span>HPACKEncoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Encode</span><span style="color:#eceff4">(</span>headers <span style="color:#eceff4">[]</span>HeaderField<span style="color:#eceff4">)</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> buf bytes<span style="color:#eceff4">.</span>Buffer
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> _<span style="color:#eceff4">,</span> hf <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> headers <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Find a match in static table</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> index<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> staticTableMap<span style="color:#eceff4">[</span>hf<span style="color:#eceff4">];</span> ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#88c0d0">encodeInt</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> index<span style="color:#eceff4">,</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">,</span> patternIndexed<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Find a name match in static table</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> index<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> staticTableNameMap<span style="color:#eceff4">[</span>hf<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">];</span> ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#88c0d0">encodeInt</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> index<span style="color:#eceff4">,</span> <span style="color:#b48ead">6</span><span style="color:#eceff4">,</span> patternLiteralIncremental<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#88c0d0">encodeString</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> hf<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			e<span style="color:#eceff4">.</span>dynamicTable<span style="color:#eceff4">.</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span>hf<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Literal with literal name</span>
</span></span><span style="display:flex;"><span>		<span style="color:#88c0d0">encodeInt</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">6</span><span style="color:#eceff4">,</span> patternLiteralIncremental<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#88c0d0">encodeString</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> hf<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#88c0d0">encodeString</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> hf<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		e<span style="color:#eceff4">.</span>dynamicTable<span style="color:#eceff4">.</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span>hf<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> buf<span style="color:#eceff4">.</span><span style="color:#88c0d0">Bytes</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">encodeInt</span><span style="color:#eceff4">(</span>buf <span style="color:#81a1c1">*</span>bytes<span style="color:#eceff4">.</span>Buffer<span style="color:#eceff4">,</span> i <span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> n <span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> pattern <span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	mask <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">(</span><span style="color:#b48ead">1</span> <span style="color:#81a1c1">&lt;&lt;</span> n<span style="color:#eceff4">)</span> <span style="color:#81a1c1">-</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#eceff4">&lt;</span> mask <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		buf<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteByte</span><span style="color:#eceff4">(</span>pattern <span style="color:#eceff4">|</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>i<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		buf<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteByte</span><span style="color:#eceff4">(</span>pattern <span style="color:#eceff4">|</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>mask<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		i <span style="color:#81a1c1">-=</span> mask
</span></span><span style="display:flex;"><span>		varint <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> binary<span style="color:#eceff4">.</span>MaxVarintLen64<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		c <span style="color:#81a1c1">:=</span> binary<span style="color:#eceff4">.</span><span style="color:#88c0d0">PutUvarint</span><span style="color:#eceff4">(</span>varint<span style="color:#eceff4">,</span> <span style="color:#81a1c1">uint64</span><span style="color:#eceff4">(</span>i<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		buf<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>varint<span style="color:#eceff4">[:</span>c<span style="color:#eceff4">])</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">encodeString</span><span style="color:#eceff4">(</span>buf <span style="color:#81a1c1">*</span>bytes<span style="color:#eceff4">.</span>Buffer<span style="color:#eceff4">,</span> s <span style="color:#81a1c1">string</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// no huffman for now</span>
</span></span><span style="display:flex;"><span>	<span style="color:#88c0d0">encodeInt</span><span style="color:#eceff4">(</span>buf<span style="color:#eceff4">,</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>s<span style="color:#eceff4">),</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0x00</span><span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// This is patternLiteral. We are encoding a raw string.</span>
</span></span><span style="display:flex;"><span>	buf<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>s<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<h3 id="writing-an-encoder">Writing an Encoder</h3>
<p>In the last part, we manually crafted our request bytes (<code>0x82</code>, <code>0x84</code>&hellip;). That was great for learning, but terrible for usability. We need an <code>HPACKEncoder</code>.</p>
<p>Our encoder will use a &ldquo;naive&rdquo; but compliant strategy to compress headers:</p>
<ol>
<li><strong>Perfect Match:</strong> Is the full header (<code>:method: GET</code>) already in the Static Table? If yes, send the 1-byte index.</li>
<li><strong>Name Match:</strong> Is just the <em>name</em> (<code>:authority</code>) in the Static Table? If yes, send the index for the name, but write the value as a string literal and instruct the server to index it.</li>
<li><strong>No Match:</strong> Send both the name and value as string literals and instruct the server to index them.</li>
</ol>
<p>This simple logic covers 90% of use cases without needing complex state tracking on the client side.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>e <span style="color:#81a1c1">*</span>HPACKEncoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Encode</span><span style="color:#eceff4">(</span>headers <span style="color:#eceff4">[]</span>HeaderField<span style="color:#eceff4">)</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">var</span> buf bytes<span style="color:#eceff4">.</span>Buffer
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">for</span> _<span style="color:#eceff4">,</span> hf <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> headers <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#616e87;font-style:italic">// Try to find a full match (Name + Value)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1;font-weight:bold">if</span> index<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> staticTableMap<span style="color:#eceff4">[</span>hf<span style="color:#eceff4">];</span> ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#88c0d0">encodeInt</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> index<span style="color:#eceff4">,</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">,</span> patternIndexed<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#616e87;font-style:italic">// Try to find a Name match</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1;font-weight:bold">if</span> index<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> staticTableNameMap<span style="color:#eceff4">[</span>hf<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">];</span> ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#88c0d0">encodeInt</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> index<span style="color:#eceff4">,</span> <span style="color:#b48ead">6</span><span style="color:#eceff4">,</span> patternLiteralIncremental<span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// Literal with Incremental Indexing</span>
</span></span><span style="display:flex;"><span>            <span style="color:#88c0d0">encodeString</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> hf<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>            e<span style="color:#eceff4">.</span>dynamicTable<span style="color:#eceff4">.</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span>hf<span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// We must track this too!</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#616e87;font-style:italic">// Send as a full literal</span>
</span></span><span style="display:flex;"><span>        <span style="color:#88c0d0">encodeInt</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">6</span><span style="color:#eceff4">,</span> patternLiteralIncremental<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#88c0d0">encodeString</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> hf<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#88c0d0">encodeString</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> hf<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>        e<span style="color:#eceff4">.</span>dynamicTable<span style="color:#eceff4">.</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span>hf<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">return</span> buf<span style="color:#eceff4">.</span><span style="color:#88c0d0">Bytes</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><h3 id="building-a-real-client">Building a Real Client</h3>
<p>Now for the satisfying part. We are going to take all that raw socket code from <code>main.go</code> and wrap it in a clean struct that mimics the standard library. Our goal here is to integrate seamlessly with Go&rsquo;s <code>net/http</code> package, allowing us to leverage familiar types.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// client.go</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> Client <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    Timeout time<span style="color:#eceff4">.</span>Duration
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">NewClient</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">*</span>Client <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>Client<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        Timeout<span style="color:#eceff4">:</span> <span style="color:#b48ead">30</span> <span style="color:#81a1c1">*</span> time<span style="color:#eceff4">.</span>Second<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>c <span style="color:#81a1c1">*</span>Client<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Do</span><span style="color:#eceff4">(</span>req <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Response<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// ... Connection and Handshake logic ...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// Convert http.Request to HPACK HeaderFields</span>
</span></span><span style="display:flex;"><span>    authority <span style="color:#81a1c1">:=</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span>Host
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">if</span> authority <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        authority <span style="color:#eceff4">=</span> req<span style="color:#eceff4">.</span>Host <span style="color:#616e87;font-style:italic">// Fallback for robustness</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    headers <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span>HeaderField<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:method&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> req<span style="color:#eceff4">.</span>Method<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:scheme&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span>Scheme<span style="color:#eceff4">},</span> 
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:authority&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> authority<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:path&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span>Path<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// ... append req.Header ...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// Encode and send the HEADERS frame</span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// ...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// Read Loop and a Subtle Bug</span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// ...</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">return</span> httpResp<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>This part was mostly straightforward. We convert to and from the <code>HeaderField</code> type. We make sure the we use the host in the request as the <code>:authority</code> value. But there is one thing that wasted more time than I&rsquo;d like to admit.</p>
<p>After sending our request, we enter a loop to read the server&rsquo;s response. This is where I hit an annoying Go concurrency bug that wasted my time. My first implementation had a <code>defer conn.Close()</code> right after dialing the connection. I figured &ldquo;Why not close the connection? This toy client will only issue a single request per connection&rdquo;. Here&rsquo;s what I was doing:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// The WRONG way</span>
</span></span><span style="display:flex;"><span>conn<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> tls<span style="color:#eceff4">.</span><span style="color:#88c0d0">Dial</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">...</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span> <span style="color:#81a1c1">...</span> <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">defer</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span> <span style="color:#616e87;font-style:italic">// Problem!</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ... send request ...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>http<span style="color:#eceff4">.</span>Response<span style="color:#eceff4">{</span> Body<span style="color:#eceff4">:</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">NopCloser</span><span style="color:#eceff4">(</span>bytes<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>bodyBytes<span style="color:#eceff4">))</span> <span style="color:#eceff4">},</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span></code></pre></div><p>The problem? <code>defer</code> executes when the function (<code>Do</code>) returns. But the user of our client needs to read the response <code>Body</code> <em>after</em> <code>Do</code> has returned. The connection was being closed before they had a chance to read it! The old version of the client would read the full response body in-line. Switching to act more like <code>http.Client</code> significantly changed when the response body is read and, in turn, changed the lifecycle of the underlying connection.</p>
<p>The fix is to remove the <code>defer</code> and instead wrap the connection itself in a custom <code>io.ReadCloser</code> that we assign to the <code>http.Response.Body</code>. When the user calls <code>resp.Body.Close()</code>, it&rsquo;s now our responsibility to close the underlying network connection. This is the standard pattern used by Go&rsquo;s own <code>net/http</code> client.</p>
<p>Here&rsquo;s the read loop that replaces the <code>...</code> above.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// The Read Loop</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> respHeaders <span style="color:#eceff4">[]</span>HeaderField
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> respBody <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    frame<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">ReadFrame</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;connection closed: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">switch</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">case</span> FrameData<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        respBody <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>respBody<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Payload<span style="color:#81a1c1">...</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">case</span> FrameHeaders<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        headers<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> hpackDec<span style="color:#eceff4">.</span><span style="color:#88c0d0">Decode</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;hpack error: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>        respHeaders <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>respHeaders<span style="color:#eceff4">,</span> headers<span style="color:#81a1c1">...</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">case</span> FrameGoAway<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#616e87;font-style:italic">// Handle server telling us to go away</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">case</span> FrameWindowUpdate<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#616e87;font-style:italic">// Handle flow control</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// The stream is finished when we receive a frame with the END_STREAM flag.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// This can be on a HEADERS frame or the final DATA frame.</span>
</span></span><span style="display:flex;"><span>    isEndFrame <span style="color:#81a1c1">:=</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#81a1c1">==</span> FrameData <span style="color:#81a1c1">||</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#81a1c1">==</span> FrameHeaders
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">if</span> isEndFrame <span style="color:#81a1c1">&amp;&amp;</span> <span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#81a1c1">&amp;</span>FlagEndStream <span style="color:#81a1c1">!=</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ... build http.Response, then assign our custom Body ...</span>
</span></span><span style="display:flex;"><span>httpResp<span style="color:#eceff4">.</span>Body <span style="color:#eceff4">=</span> <span style="color:#81a1c1">&amp;</span>responseBody<span style="color:#eceff4">{</span>bytes<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>respBody<span style="color:#eceff4">),</span> conn<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">return</span> httpResp<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// And the helper struct that makes this possible:</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> responseBody <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">*</span>bytes<span style="color:#eceff4">.</span>Reader
</span></span><span style="display:flex;"><span>    conn io<span style="color:#eceff4">.</span>Closer
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>rb <span style="color:#81a1c1">*</span>responseBody<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">return</span> rb<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><details >
    <summary>
        full client.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-4/go/client.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;bytes&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;crypto/tls&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;encoding/binary&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">const</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Protocol constants</span>
</span></span><span style="display:flex;"><span>	Preface <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n&#34;</span>
</span></span><span style="display:flex;"><span>	Server  <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;kmcd.dev:443&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Frame Types (RFC 9113 Section 6)</span>
</span></span><span style="display:flex;"><span>	FrameData         <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x0</span>
</span></span><span style="display:flex;"><span>	FrameHeaders      <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x1</span>
</span></span><span style="display:flex;"><span>	FramePriority     <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x2</span>
</span></span><span style="display:flex;"><span>	FrameRstStream    <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x3</span>
</span></span><span style="display:flex;"><span>	FrameSettings     <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x4</span>
</span></span><span style="display:flex;"><span>	FramePushPromise  <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x5</span>
</span></span><span style="display:flex;"><span>	FramePing         <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x6</span>
</span></span><span style="display:flex;"><span>	FrameGoAway       <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x7</span>
</span></span><span style="display:flex;"><span>	FrameWindowUpdate <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x8</span>
</span></span><span style="display:flex;"><span>	FrameContinuation <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x9</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Common Flags</span>
</span></span><span style="display:flex;"><span>	FlagAck        <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x01</span> <span style="color:#616e87;font-style:italic">// For SETTINGS/PING</span>
</span></span><span style="display:flex;"><span>	FlagEndStream  <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x01</span> <span style="color:#616e87;font-style:italic">// For DATA/HEADERS</span>
</span></span><span style="display:flex;"><span>	FlagEndHeaders <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x04</span> <span style="color:#616e87;font-style:italic">// For HEADERS/PUSH_PROMISE/CONTINUATION</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// responseBody implements io.ReadCloser. It reads from the response body</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// buffer and closes the underlying connection when Close is called.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> responseBody <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1">*</span>bytes<span style="color:#eceff4">.</span>Reader
</span></span><span style="display:flex;"><span>	conn io<span style="color:#eceff4">.</span>Closer
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>rb <span style="color:#81a1c1">*</span>responseBody<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Closing response body and underlying connection.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> rb<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> Client <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Timeout time<span style="color:#eceff4">.</span>Duration
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">NewClient</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">*</span>Client <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>Client<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Timeout<span style="color:#eceff4">:</span> <span style="color:#b48ead">30</span> <span style="color:#81a1c1">*</span> time<span style="color:#eceff4">.</span>Second<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>c <span style="color:#81a1c1">*</span>Client<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Do</span><span style="color:#eceff4">(</span>req <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Response<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Setup TLS with ALPN</span>
</span></span><span style="display:flex;"><span>	config <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>tls<span style="color:#eceff4">.</span>Config<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		NextProtos<span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#34;h2&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	hpackDec <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">NewHPACKDecoder</span><span style="color:#eceff4">(</span><span style="color:#b48ead">4096</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	hpackEnc <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">NewHPACKEncoder</span><span style="color:#eceff4">(</span><span style="color:#b48ead">4096</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	port <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;443&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span><span style="color:#88c0d0">Port</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		port <span style="color:#eceff4">=</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span><span style="color:#88c0d0">Port</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	conn<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> tls<span style="color:#eceff4">.</span><span style="color:#88c0d0">Dial</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;tcp&#34;</span><span style="color:#eceff4">,</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span><span style="color:#88c0d0">Hostname</span><span style="color:#eceff4">()</span><span style="color:#81a1c1">+</span><span style="color:#a3be8c">&#34;:&#34;</span><span style="color:#81a1c1">+</span>port<span style="color:#eceff4">,</span> config<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to connect: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Do NOT defer conn.Close(). The response body wrapper will be responsible for it.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	state <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">ConnectionState</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#34;h2&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span> <span style="color:#616e87;font-style:italic">// Close connection if h2 is not negotiated</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;server did not negotiate HTTP/2: %s&#34;</span><span style="color:#eceff4">,</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Connected to %s using %s\n&#34;</span><span style="color:#eceff4">,</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span>Host<span style="color:#eceff4">,</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Send Connection Preface</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>Preface<span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to send preface: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Preface sent.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Initial Handshake Loop (using the Client&#39;s hpackDec)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Send our initial empty settings</span>
</span></span><span style="display:flex;"><span>	mySettings <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> FrameSettings<span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>mySettings<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	serverSettingsAcked <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>	mySettingsAcked <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">!</span>serverSettingsAcked <span style="color:#81a1c1">||</span> <span style="color:#eceff4">!</span>mySettingsAcked <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		frame<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">ReadFrame</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// ReadFrame needs the raw conn</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;handshake read error: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; [Handshake] Frame Type=%d, Flags=%d, Stream=%d\n&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>StreamID<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">switch</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameSettings<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#81a1c1">&amp;</span>FlagAck <span style="color:#81a1c1">!=</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				mySettingsAcked <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>				fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; Server ACK&#39;d our settings&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				ack <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> FrameSettings<span style="color:#eceff4">,</span> FlagAck<span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>				conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>ack<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>				serverSettingsAcked <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>				fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&gt;&gt;&gt; Sent SETTINGS ACK&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameWindowUpdate<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; Server provided flow control window&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameGoAway<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;server sent GOAWAY during handshake&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Now the actual request sending logic from previous Do method</span>
</span></span><span style="display:flex;"><span>	authority <span style="color:#81a1c1">:=</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span>Host
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> authority <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		authority <span style="color:#eceff4">=</span> req<span style="color:#eceff4">.</span>Host <span style="color:#616e87;font-style:italic">// Fallback if URL.Host is empty</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	scheme <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;https&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span>Scheme <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		scheme <span style="color:#eceff4">=</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span>Scheme
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	headers <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span>HeaderField<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:method&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> req<span style="color:#eceff4">.</span>Method<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:scheme&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> scheme<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:authority&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> authority<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:path&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span>Path<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> name<span style="color:#eceff4">,</span> values <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> req<span style="color:#eceff4">.</span>Header <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">for</span> _<span style="color:#eceff4">,</span> value <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> values <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			headers <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>headers<span style="color:#eceff4">,</span> HeaderField<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> name<span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> value<span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	requestPayload <span style="color:#81a1c1">:=</span> hpackEnc<span style="color:#eceff4">.</span><span style="color:#88c0d0">Encode</span><span style="color:#eceff4">(</span>headers<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	header <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">9</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	payloadLen <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>requestPayload<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>payloadLen <span style="color:#81a1c1">&gt;&gt;</span> <span style="color:#b48ead">16</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>payloadLen <span style="color:#81a1c1">&gt;&gt;</span> <span style="color:#b48ead">8</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">2</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>payloadLen<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">3</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> FrameHeaders
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">4</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> FlagEndStream <span style="color:#eceff4">|</span> FlagEndHeaders
</span></span><span style="display:flex;"><span>	binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">PutUint32</span><span style="color:#eceff4">(</span>header<span style="color:#eceff4">[</span><span style="color:#b48ead">5</span><span style="color:#eceff4">:</span><span style="color:#b48ead">9</span><span style="color:#eceff4">],</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>header<span style="color:#eceff4">,</span> requestPayload<span style="color:#81a1c1">...</span><span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to send request: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&gt;&gt;&gt; Sent HEADERS (Stream 1)&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Read response (using the conn local to Do)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> respHeaders <span style="color:#eceff4">[]</span>HeaderField
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> respBody <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		frame<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">ReadFrame</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// ReadFrame needs the raw conn</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;connection closed: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; Frame Type=%d, Flags=%d, Stream=%d\n&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>StreamID<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">switch</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameData<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			respBody <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>respBody<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Payload<span style="color:#81a1c1">...</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;      [DATA] Length=%d\n&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameHeaders<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;      [HEADERS] Decoding...&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			headers<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> hpackDec<span style="color:#eceff4">.</span><span style="color:#88c0d0">Decode</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;hpack error: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			respHeaders <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>respHeaders<span style="color:#eceff4">,</span> headers<span style="color:#81a1c1">...</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameGoAway<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			lastStream <span style="color:#81a1c1">:=</span> binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uint32</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">:</span><span style="color:#b48ead">4</span><span style="color:#eceff4">])</span> <span style="color:#81a1c1">&amp;</span> <span style="color:#b48ead">0x7FFFFFFF</span>
</span></span><span style="display:flex;"><span>			errCode <span style="color:#81a1c1">:=</span> binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uint32</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">[</span><span style="color:#b48ead">4</span><span style="color:#eceff4">:</span><span style="color:#b48ead">8</span><span style="color:#eceff4">])</span>
</span></span><span style="display:flex;"><span>			conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;GOAWAY: Last Stream %d, Error Code %d&#34;</span><span style="color:#eceff4">,</span> lastStream<span style="color:#eceff4">,</span> errCode<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameWindowUpdate<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;      [WINDOW_UPDATE]&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		isEndFrame <span style="color:#81a1c1">:=</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#81a1c1">==</span> FrameData <span style="color:#81a1c1">||</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#81a1c1">==</span> FrameHeaders
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> isEndFrame <span style="color:#81a1c1">&amp;&amp;</span> <span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#81a1c1">&amp;</span>FlagEndStream <span style="color:#81a1c1">!=</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Stream finished.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Build http.Response</span>
</span></span><span style="display:flex;"><span>	httpResp <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>http<span style="color:#eceff4">.</span>Response<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		StatusCode<span style="color:#eceff4">:</span> <span style="color:#b48ead">200</span><span style="color:#eceff4">,</span> <span style="color:#616e87;font-style:italic">// Hardcoded for now</span>
</span></span><span style="display:flex;"><span>		Proto<span style="color:#eceff4">:</span>      <span style="color:#a3be8c">&#34;HTTP/2.0&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		ProtoMajor<span style="color:#eceff4">:</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		ProtoMinor<span style="color:#eceff4">:</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		Header<span style="color:#eceff4">:</span>     <span style="color:#81a1c1">make</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>		Body<span style="color:#eceff4">:</span>       <span style="color:#81a1c1">&amp;</span>responseBody<span style="color:#eceff4">{</span>bytes<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>respBody<span style="color:#eceff4">),</span> conn<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> _<span style="color:#eceff4">,</span> h <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> respHeaders <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		httpResp<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span>h<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">,</span> h<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> h<span style="color:#eceff4">.</span>Name <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;:status&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sscanf</span><span style="color:#eceff4">(</span>h<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;%d&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">&amp;</span>httpResp<span style="color:#eceff4">.</span>StatusCode<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	httpResp<span style="color:#eceff4">.</span>Status <span style="color:#eceff4">=</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sprintf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;%d %s&#34;</span><span style="color:#eceff4">,</span> httpResp<span style="color:#eceff4">.</span>StatusCode<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">StatusText</span><span style="color:#eceff4">(</span>httpResp<span style="color:#eceff4">.</span>StatusCode<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> httpResp<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<h3 id="the-result">The Result</h3>
<p>With this refactor, our <code>main.go</code> transforms from a mess of magical hex values into clean, idiomatic Go:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    client <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">NewClient</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>    req<span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewRequest</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;GET&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;https://kmcd.dev/&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    resp<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> client<span style="color:#eceff4">.</span><span style="color:#88c0d0">Do</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatal</span><span style="color:#eceff4">(</span>err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// This now correctly closes our underlying TCP connection.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">defer</span> resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Status: %s\n&#34;</span><span style="color:#eceff4">,</span> resp<span style="color:#eceff4">.</span>Status<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    body<span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadAll</span><span style="color:#eceff4">(</span>resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Body Length: %d bytes\n&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>body<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><h3 id="limitations">Limitations</h3>
<p>While we have a working client, it is strictly a &ldquo;happy path&rdquo; implementation:</p>
<ul>
<li><strong>Concurrency:</strong> Our client is synchronous. It sends a request and waits. Real HTTP/2 uses multiplexing to send many requests at once over a single connection.</li>
<li><strong>Flow Control:</strong> We are ignoring <code>WINDOW_UPDATE</code> frames. If we tried to download a large file, our connection would stall once the window fills up.</li>
<li><strong>Connection Re-use:</strong> We create a new TCP connection for every <code>Do</code> call. Because the HPACK Dynamic Table is tied to the connection, we aren&rsquo;t actually gaining any compression benefits across multiple requests.</li>
<li><strong>Many, Many Other Features:</strong> There is a lot of small details that this toy client completely glosses over. For example, trailer support. This is funny, because I actually added trailer support for <a href="https://github.com/quic-go/quic-go" rel="external">quic-go</a>: <a href="https://github.com/quic-go/quic-go/pull/4581" rel="external">#4581</a>, <a href="https://github.com/quic-go/quic-go/pull/4630" rel="external">#4630</a>. You can read up more about this when writing my <a href="https://kmcd.dev/posts/grpc-over-http3/">gRPC over HTTP/3</a> series.</li>
</ul>
<p>Getting this far, I have a renewed and massive respect for the <code>net/http</code> maintainers. We started this series with hex dumps and now have a client struct that respects <code>io.Closer</code>. The &ldquo;happy path&rdquo; alone is a journey through specifications, bit-masking, and subtle concurrency bugs. Handling real-world network conditions, multiplexing, and flow control is a monumental task that makes you appreciate the standard library on a new level.</p>
<p>For now, I&rsquo;m happy with this victory. We&rsquo;ve built a real, working HTTP/2 client.</p>
<h3 id="next-steps">Next Steps</h3>
<p>Now that we&rsquo;ve implemented a decent amount of HTTP/2, I think the next step is to delve into QUIC and HTTP/3. I mentioned earlier that HTTP/2 solves the head-of-line blocking issue. That&rsquo;s only partially true. Yes, at the application layer HTTP/2 doesn&rsquo;t have a head-of-line blocking issue but since HTTP/2 is built on top of TCP, it inherits sequential packet ordering. This is normally an amazing feature of TCP but in this case it actually hinders us. Since HTTP/2 multiplexes many independent streams over a single TCP connection, packet loss affecting stream A will still block delivery for stream B. The only way around this is to make significant updates to TCP (not going to happen) or completely abandon TCP altogether.</p>
<p>This, along with 0-RTT connection resumption (sending data before the handshake completes), are things that just aren&rsquo;t possible to side-step using HTTP/2. This is why QUIC and HTTP/3 were created. But you&rsquo;ll have to wait a bit longer before seeing me implement that from scratch.</p>
<p>See all of the code mentioned in this article here:
<details >
    <summary>
        go/client.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-4/go/client.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;bytes&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;crypto/tls&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;encoding/binary&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">const</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Protocol constants</span>
</span></span><span style="display:flex;"><span>	Preface <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n&#34;</span>
</span></span><span style="display:flex;"><span>	Server  <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;kmcd.dev:443&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Frame Types (RFC 9113 Section 6)</span>
</span></span><span style="display:flex;"><span>	FrameData         <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x0</span>
</span></span><span style="display:flex;"><span>	FrameHeaders      <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x1</span>
</span></span><span style="display:flex;"><span>	FramePriority     <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x2</span>
</span></span><span style="display:flex;"><span>	FrameRstStream    <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x3</span>
</span></span><span style="display:flex;"><span>	FrameSettings     <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x4</span>
</span></span><span style="display:flex;"><span>	FramePushPromise  <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x5</span>
</span></span><span style="display:flex;"><span>	FramePing         <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x6</span>
</span></span><span style="display:flex;"><span>	FrameGoAway       <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x7</span>
</span></span><span style="display:flex;"><span>	FrameWindowUpdate <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x8</span>
</span></span><span style="display:flex;"><span>	FrameContinuation <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x9</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Common Flags</span>
</span></span><span style="display:flex;"><span>	FlagAck        <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x01</span> <span style="color:#616e87;font-style:italic">// For SETTINGS/PING</span>
</span></span><span style="display:flex;"><span>	FlagEndStream  <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x01</span> <span style="color:#616e87;font-style:italic">// For DATA/HEADERS</span>
</span></span><span style="display:flex;"><span>	FlagEndHeaders <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x04</span> <span style="color:#616e87;font-style:italic">// For HEADERS/PUSH_PROMISE/CONTINUATION</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// responseBody implements io.ReadCloser. It reads from the response body</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// buffer and closes the underlying connection when Close is called.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> responseBody <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1">*</span>bytes<span style="color:#eceff4">.</span>Reader
</span></span><span style="display:flex;"><span>	conn io<span style="color:#eceff4">.</span>Closer
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>rb <span style="color:#81a1c1">*</span>responseBody<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Closing response body and underlying connection.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> rb<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> Client <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Timeout time<span style="color:#eceff4">.</span>Duration
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">NewClient</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">*</span>Client <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>Client<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Timeout<span style="color:#eceff4">:</span> <span style="color:#b48ead">30</span> <span style="color:#81a1c1">*</span> time<span style="color:#eceff4">.</span>Second<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>c <span style="color:#81a1c1">*</span>Client<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Do</span><span style="color:#eceff4">(</span>req <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Response<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Setup TLS with ALPN</span>
</span></span><span style="display:flex;"><span>	config <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>tls<span style="color:#eceff4">.</span>Config<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		NextProtos<span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#34;h2&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	hpackDec <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">NewHPACKDecoder</span><span style="color:#eceff4">(</span><span style="color:#b48ead">4096</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	hpackEnc <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">NewHPACKEncoder</span><span style="color:#eceff4">(</span><span style="color:#b48ead">4096</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	port <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;443&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span><span style="color:#88c0d0">Port</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		port <span style="color:#eceff4">=</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span><span style="color:#88c0d0">Port</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	conn<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> tls<span style="color:#eceff4">.</span><span style="color:#88c0d0">Dial</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;tcp&#34;</span><span style="color:#eceff4">,</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span><span style="color:#88c0d0">Hostname</span><span style="color:#eceff4">()</span><span style="color:#81a1c1">+</span><span style="color:#a3be8c">&#34;:&#34;</span><span style="color:#81a1c1">+</span>port<span style="color:#eceff4">,</span> config<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to connect: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Do NOT defer conn.Close(). The response body wrapper will be responsible for it.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	state <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">ConnectionState</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#34;h2&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span> <span style="color:#616e87;font-style:italic">// Close connection if h2 is not negotiated</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;server did not negotiate HTTP/2: %s&#34;</span><span style="color:#eceff4">,</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Connected to %s using %s\n&#34;</span><span style="color:#eceff4">,</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span>Host<span style="color:#eceff4">,</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Send Connection Preface</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>Preface<span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to send preface: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Preface sent.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Initial Handshake Loop (using the Client&#39;s hpackDec)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Send our initial empty settings</span>
</span></span><span style="display:flex;"><span>	mySettings <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> FrameSettings<span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>mySettings<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	serverSettingsAcked <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>	mySettingsAcked <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">!</span>serverSettingsAcked <span style="color:#81a1c1">||</span> <span style="color:#eceff4">!</span>mySettingsAcked <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		frame<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">ReadFrame</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// ReadFrame needs the raw conn</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;handshake read error: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; [Handshake] Frame Type=%d, Flags=%d, Stream=%d\n&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>StreamID<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">switch</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameSettings<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#81a1c1">&amp;</span>FlagAck <span style="color:#81a1c1">!=</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				mySettingsAcked <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>				fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; Server ACK&#39;d our settings&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				ack <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> FrameSettings<span style="color:#eceff4">,</span> FlagAck<span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>				conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>ack<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>				serverSettingsAcked <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>				fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&gt;&gt;&gt; Sent SETTINGS ACK&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameWindowUpdate<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; Server provided flow control window&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameGoAway<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;server sent GOAWAY during handshake&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Now the actual request sending logic from previous Do method</span>
</span></span><span style="display:flex;"><span>	authority <span style="color:#81a1c1">:=</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span>Host
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> authority <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		authority <span style="color:#eceff4">=</span> req<span style="color:#eceff4">.</span>Host <span style="color:#616e87;font-style:italic">// Fallback if URL.Host is empty</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	scheme <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;https&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span>Scheme <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		scheme <span style="color:#eceff4">=</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span>Scheme
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	headers <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span>HeaderField<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:method&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> req<span style="color:#eceff4">.</span>Method<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:scheme&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> scheme<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:authority&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> authority<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:path&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">.</span>Path<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> name<span style="color:#eceff4">,</span> values <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> req<span style="color:#eceff4">.</span>Header <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">for</span> _<span style="color:#eceff4">,</span> value <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> values <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			headers <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>headers<span style="color:#eceff4">,</span> HeaderField<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> name<span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> value<span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	requestPayload <span style="color:#81a1c1">:=</span> hpackEnc<span style="color:#eceff4">.</span><span style="color:#88c0d0">Encode</span><span style="color:#eceff4">(</span>headers<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	header <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">9</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	payloadLen <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>requestPayload<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>payloadLen <span style="color:#81a1c1">&gt;&gt;</span> <span style="color:#b48ead">16</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>payloadLen <span style="color:#81a1c1">&gt;&gt;</span> <span style="color:#b48ead">8</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">2</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>payloadLen<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">3</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> FrameHeaders
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">4</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> FlagEndStream <span style="color:#eceff4">|</span> FlagEndHeaders
</span></span><span style="display:flex;"><span>	binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">PutUint32</span><span style="color:#eceff4">(</span>header<span style="color:#eceff4">[</span><span style="color:#b48ead">5</span><span style="color:#eceff4">:</span><span style="color:#b48ead">9</span><span style="color:#eceff4">],</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>header<span style="color:#eceff4">,</span> requestPayload<span style="color:#81a1c1">...</span><span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to send request: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&gt;&gt;&gt; Sent HEADERS (Stream 1)&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Read response (using the conn local to Do)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> respHeaders <span style="color:#eceff4">[]</span>HeaderField
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> respBody <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		frame<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">ReadFrame</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// ReadFrame needs the raw conn</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;connection closed: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; Frame Type=%d, Flags=%d, Stream=%d\n&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>StreamID<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">switch</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameData<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			respBody <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>respBody<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Payload<span style="color:#81a1c1">...</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;      [DATA] Length=%d\n&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameHeaders<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;      [HEADERS] Decoding...&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			headers<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> hpackDec<span style="color:#eceff4">.</span><span style="color:#88c0d0">Decode</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;hpack error: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			respHeaders <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>respHeaders<span style="color:#eceff4">,</span> headers<span style="color:#81a1c1">...</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameGoAway<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			lastStream <span style="color:#81a1c1">:=</span> binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uint32</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">:</span><span style="color:#b48ead">4</span><span style="color:#eceff4">])</span> <span style="color:#81a1c1">&amp;</span> <span style="color:#b48ead">0x7FFFFFFF</span>
</span></span><span style="display:flex;"><span>			errCode <span style="color:#81a1c1">:=</span> binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uint32</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">[</span><span style="color:#b48ead">4</span><span style="color:#eceff4">:</span><span style="color:#b48ead">8</span><span style="color:#eceff4">])</span>
</span></span><span style="display:flex;"><span>			conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;GOAWAY: Last Stream %d, Error Code %d&#34;</span><span style="color:#eceff4">,</span> lastStream<span style="color:#eceff4">,</span> errCode<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameWindowUpdate<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;      [WINDOW_UPDATE]&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		isEndFrame <span style="color:#81a1c1">:=</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#81a1c1">==</span> FrameData <span style="color:#81a1c1">||</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#81a1c1">==</span> FrameHeaders
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> isEndFrame <span style="color:#81a1c1">&amp;&amp;</span> <span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#81a1c1">&amp;</span>FlagEndStream <span style="color:#81a1c1">!=</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Stream finished.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Build http.Response</span>
</span></span><span style="display:flex;"><span>	httpResp <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>http<span style="color:#eceff4">.</span>Response<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		StatusCode<span style="color:#eceff4">:</span> <span style="color:#b48ead">200</span><span style="color:#eceff4">,</span> <span style="color:#616e87;font-style:italic">// Hardcoded for now</span>
</span></span><span style="display:flex;"><span>		Proto<span style="color:#eceff4">:</span>      <span style="color:#a3be8c">&#34;HTTP/2.0&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		ProtoMajor<span style="color:#eceff4">:</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		ProtoMinor<span style="color:#eceff4">:</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		Header<span style="color:#eceff4">:</span>     <span style="color:#81a1c1">make</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>		Body<span style="color:#eceff4">:</span>       <span style="color:#81a1c1">&amp;</span>responseBody<span style="color:#eceff4">{</span>bytes<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>respBody<span style="color:#eceff4">),</span> conn<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> _<span style="color:#eceff4">,</span> h <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> respHeaders <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		httpResp<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span>h<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">,</span> h<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> h<span style="color:#eceff4">.</span>Name <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;:status&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sscanf</span><span style="color:#eceff4">(</span>h<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;%d&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">&amp;</span>httpResp<span style="color:#eceff4">.</span>StatusCode<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	httpResp<span style="color:#eceff4">.</span>Status <span style="color:#eceff4">=</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sprintf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;%d %s&#34;</span><span style="color:#eceff4">,</span> httpResp<span style="color:#eceff4">.</span>StatusCode<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">StatusText</span><span style="color:#eceff4">(</span>httpResp<span style="color:#eceff4">.</span>StatusCode<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> httpResp<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<details >
    <summary>
        go/hpack.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-4/go/hpack.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;bytes&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;encoding/binary&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;golang.org/x/net/http2/hpack&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// RFC 7541: HPACK: Header Compression for HTTP/2</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// https://datatracker.ietf.org/doc/html/rfc7541</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">const</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Masks for HPACK header field types</span>
</span></span><span style="display:flex;"><span>	maskIndexed            <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x80</span> <span style="color:#616e87;font-style:italic">// 10000000</span>
</span></span><span style="display:flex;"><span>	maskLiteralIncremental <span style="color:#eceff4">=</span> <span style="color:#b48ead">0xc0</span> <span style="color:#616e87;font-style:italic">// 11000000</span>
</span></span><span style="display:flex;"><span>	maskDynamicTableSize   <span style="color:#eceff4">=</span> <span style="color:#b48ead">0xe0</span> <span style="color:#616e87;font-style:italic">// 11100000</span>
</span></span><span style="display:flex;"><span>	maskLiteral            <span style="color:#eceff4">=</span> <span style="color:#b48ead">0xf0</span> <span style="color:#616e87;font-style:italic">// 11110000</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Patterns for HPACK header field types</span>
</span></span><span style="display:flex;"><span>	patternIndexed            <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x80</span> <span style="color:#616e87;font-style:italic">// 10000000</span>
</span></span><span style="display:flex;"><span>	patternLiteralIncremental <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x40</span> <span style="color:#616e87;font-style:italic">// 01000000</span>
</span></span><span style="display:flex;"><span>	patternDynamicTableSize   <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x20</span> <span style="color:#616e87;font-style:italic">// 00100000</span>
</span></span><span style="display:flex;"><span>	patternLiteralNever       <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x10</span> <span style="color:#616e87;font-style:italic">// 00010000</span>
</span></span><span style="display:flex;"><span>	patternLiteral            <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x00</span> <span style="color:#616e87;font-style:italic">// 00000000</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	HuffmanFlagMask         <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x80</span>
</span></span><span style="display:flex;"><span>	IntegerContinuationMask <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x80</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// HeaderField represents a header field with a name and value.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> HeaderField <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Name<span style="color:#eceff4">,</span> Value <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// StaticTable is the predefined, unchangeable table of header fields, as defined in RFC 7541 Appendix A.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> StaticTable <span style="color:#eceff4">=</span> <span style="color:#eceff4">[]</span>HeaderField<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:authority&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:method&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;GET&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:method&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;POST&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:path&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;/&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:path&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;/index.html&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:scheme&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;http&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:scheme&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;200&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;204&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;206&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;304&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;400&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;404&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;500&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-charset&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-encoding&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;gzip, deflate&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-language&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-ranges&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;access-control-allow-origin&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;age&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;allow&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;authorization&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;cache-control&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-disposition&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-encoding&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-language&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-length&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-location&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-range&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-type&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;cookie&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;date&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;etag&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;expect&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;expires&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;from&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;host&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-match&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-modified-since&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-none-match&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-range&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-unmodified-since&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;last-modified&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;link&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;location&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;max-forwards&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;proxy-authenticate&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;proxy-authorization&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;range&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;referer&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;retry-after&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;server&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;set-cookie&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;strict-transport-security&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;transfer-encoding&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;user-agent&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;vary&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;via&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;www-authenticate&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	staticTableMap     <span style="color:#eceff4">=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">map</span><span style="color:#eceff4">[</span>HeaderField<span style="color:#eceff4">]</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	staticTableNameMap <span style="color:#eceff4">=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">map</span><span style="color:#eceff4">[</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">]</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">init</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> i<span style="color:#eceff4">,</span> hf <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> StaticTable <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		staticTableMap<span style="color:#eceff4">[</span>hf<span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> i <span style="color:#81a1c1">+</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> staticTableNameMap<span style="color:#eceff4">[</span>hf<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">];</span> <span style="color:#eceff4">!</span>ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			staticTableNameMap<span style="color:#eceff4">[</span>hf<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> i <span style="color:#81a1c1">+</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> DynamicTable <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	headers <span style="color:#eceff4">[]</span>HeaderField
</span></span><span style="display:flex;"><span>	size    <span style="color:#81a1c1">uint32</span>
</span></span><span style="display:flex;"><span>	maxSize <span style="color:#81a1c1">uint32</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">NewDynamicTable</span><span style="color:#eceff4">(</span>maxSize <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">*</span>DynamicTable <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>DynamicTable<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		maxSize<span style="color:#eceff4">:</span> maxSize<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>d <span style="color:#81a1c1">*</span>DynamicTable<span style="color:#eceff4">)</span> <span style="color:#88c0d0">At</span><span style="color:#eceff4">(</span>i <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>HeaderField<span style="color:#eceff4">,</span> <span style="color:#81a1c1">bool</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#81a1c1">||</span> i <span style="color:#81a1c1">&gt;=</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> HeaderField<span style="color:#eceff4">{},</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">[</span>i<span style="color:#eceff4">],</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>d <span style="color:#81a1c1">*</span>DynamicTable<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span>h HeaderField<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	size <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>h<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">)</span> <span style="color:#81a1c1">+</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>h<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span> <span style="color:#81a1c1">+</span> <span style="color:#b48ead">32</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> d<span style="color:#eceff4">.</span>size<span style="color:#81a1c1">+</span>size <span style="color:#eceff4">&gt;</span> d<span style="color:#eceff4">.</span>maxSize <span style="color:#81a1c1">&amp;&amp;</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">)</span> <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		last <span style="color:#81a1c1">:=</span> d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">[</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">)</span><span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>		d<span style="color:#eceff4">.</span>size <span style="color:#81a1c1">-=</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>last<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">)</span> <span style="color:#81a1c1">+</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>last<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span> <span style="color:#81a1c1">+</span> <span style="color:#b48ead">32</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		d<span style="color:#eceff4">.</span>headers <span style="color:#eceff4">=</span> d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">[:</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">)</span><span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	d<span style="color:#eceff4">.</span>headers <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">([]</span>HeaderField<span style="color:#eceff4">{</span>h<span style="color:#eceff4">},</span> d<span style="color:#eceff4">.</span>headers<span style="color:#81a1c1">...</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	d<span style="color:#eceff4">.</span>size <span style="color:#81a1c1">+=</span> size
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>d <span style="color:#81a1c1">*</span>DynamicTable<span style="color:#eceff4">)</span> <span style="color:#88c0d0">SetMaxSize</span><span style="color:#eceff4">(</span>size <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	d<span style="color:#eceff4">.</span>maxSize <span style="color:#eceff4">=</span> size
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> d<span style="color:#eceff4">.</span>size <span style="color:#eceff4">&gt;</span> d<span style="color:#eceff4">.</span>maxSize <span style="color:#81a1c1">&amp;&amp;</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">)</span> <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		last <span style="color:#81a1c1">:=</span> d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">[</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">)</span><span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>		d<span style="color:#eceff4">.</span>size <span style="color:#81a1c1">-=</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>last<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">)</span> <span style="color:#81a1c1">+</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>last<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span> <span style="color:#81a1c1">+</span> <span style="color:#b48ead">32</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		d<span style="color:#eceff4">.</span>headers <span style="color:#eceff4">=</span> d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">[:</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>d<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">)</span><span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> HPACKDecoder <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	dynamicTable <span style="color:#81a1c1">*</span>DynamicTable
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">NewHPACKDecoder</span><span style="color:#eceff4">(</span>maxSize <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">*</span>HPACKDecoder <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>HPACKDecoder<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		dynamicTable<span style="color:#eceff4">:</span> <span style="color:#88c0d0">NewDynamicTable</span><span style="color:#eceff4">(</span>maxSize<span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>h <span style="color:#81a1c1">*</span>HPACKDecoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Header</span><span style="color:#eceff4">(</span>i <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>HeaderField<span style="color:#eceff4">,</span> <span style="color:#81a1c1">bool</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#81a1c1">&lt;=</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> HeaderField<span style="color:#eceff4">{},</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#81a1c1">&lt;=</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>StaticTable<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> StaticTable<span style="color:#eceff4">[</span>i<span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span><span style="color:#eceff4">],</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> h<span style="color:#eceff4">.</span>dynamicTable<span style="color:#eceff4">.</span><span style="color:#88c0d0">At</span><span style="color:#eceff4">(</span>i <span style="color:#81a1c1">-</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>StaticTable<span style="color:#eceff4">)</span> <span style="color:#81a1c1">-</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>h <span style="color:#81a1c1">*</span>HPACKDecoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Decode</span><span style="color:#eceff4">(</span>payload <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">([]</span>HeaderField<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> headers <span style="color:#eceff4">[]</span>HeaderField
</span></span><span style="display:flex;"><span>	r <span style="color:#81a1c1">:=</span> bytes<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">Len</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		b<span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadByte</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span>maskIndexed <span style="color:#81a1c1">==</span> patternIndexed <span style="color:#eceff4">{</span> <span style="color:#616e87;font-style:italic">// Indexed Header Field</span>
</span></span><span style="display:flex;"><span>			index<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">,</span> r<span style="color:#eceff4">,</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> n <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode integer&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			header<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">(</span>index<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid header index: %d&#34;</span><span style="color:#eceff4">,</span> index<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			headers <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>headers<span style="color:#eceff4">,</span> header<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;  [Header] %s: %s\n&#34;</span><span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span>maskLiteralIncremental <span style="color:#81a1c1">==</span> patternLiteralIncremental <span style="color:#eceff4">{</span> <span style="color:#616e87;font-style:italic">// Literal Header Field with Incremental Indexing</span>
</span></span><span style="display:flex;"><span>			index<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">,</span> r<span style="color:#eceff4">,</span> <span style="color:#b48ead">6</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> n <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode integer&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			header<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">decodeLiteralHeader</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">,</span> index<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			headers <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>headers<span style="color:#eceff4">,</span> header<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;  [Header] %s: %s\n&#34;</span><span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span>maskLiteral <span style="color:#81a1c1">==</span> patternLiteral <span style="color:#81a1c1">||</span> b<span style="color:#81a1c1">&amp;</span>maskLiteral <span style="color:#81a1c1">==</span> patternLiteralNever <span style="color:#eceff4">{</span> <span style="color:#616e87;font-style:italic">// Literal Header Field without or never indexed</span>
</span></span><span style="display:flex;"><span>			index<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">,</span> r<span style="color:#eceff4">,</span> <span style="color:#b48ead">4</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> n <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode integer&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			header<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">decodeLiteralHeader</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">,</span> index<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">false</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			headers <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>headers<span style="color:#eceff4">,</span> header<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;  [Header] %s: %s\n&#34;</span><span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span>maskDynamicTableSize <span style="color:#81a1c1">==</span> patternDynamicTableSize <span style="color:#eceff4">{</span> <span style="color:#616e87;font-style:italic">// Dynamic Table Size Update</span>
</span></span><span style="display:flex;"><span>			size<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">,</span> r<span style="color:#eceff4">,</span> <span style="color:#b48ead">5</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> n <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode integer&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			h<span style="color:#eceff4">.</span>dynamicTable<span style="color:#eceff4">.</span><span style="color:#88c0d0">SetMaxSize</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>size<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;not implemented: unknown header field type %08b&#34;</span><span style="color:#eceff4">,</span> b<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> headers<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>h <span style="color:#81a1c1">*</span>HPACKDecoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">decodeLiteralHeader</span><span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>bytes<span style="color:#eceff4">.</span>Reader<span style="color:#eceff4">,</span> index <span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> addToDynamicTable <span style="color:#81a1c1">bool</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>HeaderField<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> name <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> err <span style="color:#81a1c1">error</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> index <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		header<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">(</span>index<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> HeaderField<span style="color:#eceff4">{},</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid header index: %d&#34;</span><span style="color:#eceff4">,</span> index<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		name <span style="color:#eceff4">=</span> header<span style="color:#eceff4">.</span>Name
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		name<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">decodeString</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> HeaderField<span style="color:#eceff4">{},</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	value<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">decodeString</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> HeaderField<span style="color:#eceff4">{},</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	header <span style="color:#81a1c1">:=</span> HeaderField<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> name<span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> value<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> addToDynamicTable <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		h<span style="color:#eceff4">.</span>dynamicTable<span style="color:#eceff4">.</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span>header<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> header<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>h <span style="color:#81a1c1">*</span>HPACKDecoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">decodeString</span><span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>bytes<span style="color:#eceff4">.</span>Reader<span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	b<span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadByte</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	huffman <span style="color:#81a1c1">:=</span> b<span style="color:#81a1c1">&amp;</span>HuffmanFlagMask <span style="color:#81a1c1">==</span> HuffmanFlagMask
</span></span><span style="display:flex;"><span>	length<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">,</span> r<span style="color:#eceff4">,</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> n <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode integer&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">Len</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">&lt;</span> length <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">,</span> io<span style="color:#eceff4">.</span>ErrUnexpectedEOF
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	data <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> length<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	r<span style="color:#eceff4">.</span><span style="color:#88c0d0">Read</span><span style="color:#eceff4">(</span>data<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> huffman <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> hpack<span style="color:#eceff4">.</span><span style="color:#88c0d0">HuffmanDecodeToString</span><span style="color:#eceff4">(</span>data<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>data<span style="color:#eceff4">),</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>b <span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>bytes<span style="color:#eceff4">.</span>Reader<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	mask <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">(</span><span style="color:#b48ead">1</span> <span style="color:#81a1c1">&lt;&lt;</span> n<span style="color:#eceff4">)</span> <span style="color:#81a1c1">-</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	i <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">)</span> <span style="color:#81a1c1">&amp;</span> mask
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#eceff4">&lt;</span> mask <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> i<span style="color:#eceff4">,</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> m <span style="color:#81a1c1">uint</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>	bytesRead <span style="color:#81a1c1">:=</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> val <span style="color:#81a1c1">uint64</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		b<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadByte</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">-</span>bytesRead
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		bytesRead<span style="color:#81a1c1">++</span>
</span></span><span style="display:flex;"><span>		val <span style="color:#81a1c1">|=</span> <span style="color:#81a1c1">uint64</span><span style="color:#eceff4">(</span>b<span style="color:#81a1c1">&amp;</span><span style="color:#b48ead">127</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">&lt;&lt;</span> m
</span></span><span style="display:flex;"><span>		m <span style="color:#81a1c1">+=</span> <span style="color:#b48ead">7</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span>IntegerContinuationMask <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> i <span style="color:#81a1c1">+</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">(</span>val<span style="color:#eceff4">),</span> bytesRead
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> HPACKEncoder <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	dynamicTable <span style="color:#81a1c1">*</span>DynamicTable
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">NewHPACKEncoder</span><span style="color:#eceff4">(</span>maxSize <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">*</span>HPACKEncoder <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>HPACKEncoder<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		dynamicTable<span style="color:#eceff4">:</span> <span style="color:#88c0d0">NewDynamicTable</span><span style="color:#eceff4">(</span>maxSize<span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>e <span style="color:#81a1c1">*</span>HPACKEncoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Encode</span><span style="color:#eceff4">(</span>headers <span style="color:#eceff4">[]</span>HeaderField<span style="color:#eceff4">)</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> buf bytes<span style="color:#eceff4">.</span>Buffer
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> _<span style="color:#eceff4">,</span> hf <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> headers <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Find a match in static table</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> index<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> staticTableMap<span style="color:#eceff4">[</span>hf<span style="color:#eceff4">];</span> ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#88c0d0">encodeInt</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> index<span style="color:#eceff4">,</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">,</span> patternIndexed<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Find a name match in static table</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> index<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> staticTableNameMap<span style="color:#eceff4">[</span>hf<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">];</span> ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#88c0d0">encodeInt</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> index<span style="color:#eceff4">,</span> <span style="color:#b48ead">6</span><span style="color:#eceff4">,</span> patternLiteralIncremental<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#88c0d0">encodeString</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> hf<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			e<span style="color:#eceff4">.</span>dynamicTable<span style="color:#eceff4">.</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span>hf<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Literal with literal name</span>
</span></span><span style="display:flex;"><span>		<span style="color:#88c0d0">encodeInt</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">6</span><span style="color:#eceff4">,</span> patternLiteralIncremental<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#88c0d0">encodeString</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> hf<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#88c0d0">encodeString</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>buf<span style="color:#eceff4">,</span> hf<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		e<span style="color:#eceff4">.</span>dynamicTable<span style="color:#eceff4">.</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span>hf<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> buf<span style="color:#eceff4">.</span><span style="color:#88c0d0">Bytes</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">encodeInt</span><span style="color:#eceff4">(</span>buf <span style="color:#81a1c1">*</span>bytes<span style="color:#eceff4">.</span>Buffer<span style="color:#eceff4">,</span> i <span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> n <span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> pattern <span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	mask <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">(</span><span style="color:#b48ead">1</span> <span style="color:#81a1c1">&lt;&lt;</span> n<span style="color:#eceff4">)</span> <span style="color:#81a1c1">-</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#eceff4">&lt;</span> mask <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		buf<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteByte</span><span style="color:#eceff4">(</span>pattern <span style="color:#eceff4">|</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>i<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		buf<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteByte</span><span style="color:#eceff4">(</span>pattern <span style="color:#eceff4">|</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>mask<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		i <span style="color:#81a1c1">-=</span> mask
</span></span><span style="display:flex;"><span>		varint <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> binary<span style="color:#eceff4">.</span>MaxVarintLen64<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		c <span style="color:#81a1c1">:=</span> binary<span style="color:#eceff4">.</span><span style="color:#88c0d0">PutUvarint</span><span style="color:#eceff4">(</span>varint<span style="color:#eceff4">,</span> <span style="color:#81a1c1">uint64</span><span style="color:#eceff4">(</span>i<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		buf<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>varint<span style="color:#eceff4">[:</span>c<span style="color:#eceff4">])</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">encodeString</span><span style="color:#eceff4">(</span>buf <span style="color:#81a1c1">*</span>bytes<span style="color:#eceff4">.</span>Buffer<span style="color:#eceff4">,</span> s <span style="color:#81a1c1">string</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// no huffman for now</span>
</span></span><span style="display:flex;"><span>	<span style="color:#88c0d0">encodeInt</span><span style="color:#eceff4">(</span>buf<span style="color:#eceff4">,</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>s<span style="color:#eceff4">),</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0x00</span><span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// This is patternLiteral. We are encoding a raw string.</span>
</span></span><span style="display:flex;"><span>	buf<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>s<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<details >
    <summary>
        go/main.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-4/go/main.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	client <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">NewClient</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewRequest</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;GET&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;https://kmcd.dev/&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to create request: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	resp<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> client<span style="color:#eceff4">.</span><span style="color:#88c0d0">Do</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to execute request: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Protocol: %s\n&#34;</span><span style="color:#eceff4">,</span> resp<span style="color:#eceff4">.</span>Proto<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Response Status:&#34;</span><span style="color:#eceff4">,</span> resp<span style="color:#eceff4">.</span>Status<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	body<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadAll</span><span style="color:#eceff4">(</span>resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to read body: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Response Body: %s\n&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>body<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<details >
    <summary>
        go/parser.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-4/go/parser.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;encoding/binary&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// FrameHeader represents the 9-byte fixed header of every HTTP/2 frame.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> FrameHeader <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Length   <span style="color:#81a1c1">uint32</span>
</span></span><span style="display:flex;"><span>	Type     <span style="color:#81a1c1">uint8</span>
</span></span><span style="display:flex;"><span>	Flags    <span style="color:#81a1c1">uint8</span>
</span></span><span style="display:flex;"><span>	StreamID <span style="color:#81a1c1">uint32</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Frame represents a complete HTTP/2 frame including its payload.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> Frame <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Header  FrameHeader
</span></span><span style="display:flex;"><span>	Payload <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ReadFrame reads a header and then the corresponding payload from the connection.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">ReadFrame</span><span style="color:#eceff4">(</span>r io<span style="color:#eceff4">.</span>Reader<span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>Frame<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Read the 9-byte header</span>
</span></span><span style="display:flex;"><span>	headerBuf <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">9</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadFull</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">,</span> headerBuf<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> Frame<span style="color:#eceff4">{},</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;reading header: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Parse the header fields using bit-shifting</span>
</span></span><span style="display:flex;"><span>	header <span style="color:#81a1c1">:=</span> FrameHeader<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Length<span style="color:#eceff4">:</span>   <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">])</span><span style="color:#81a1c1">&lt;&lt;</span><span style="color:#b48ead">16</span> <span style="color:#eceff4">|</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">])</span><span style="color:#81a1c1">&lt;&lt;</span><span style="color:#b48ead">8</span> <span style="color:#eceff4">|</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">2</span><span style="color:#eceff4">]),</span>
</span></span><span style="display:flex;"><span>		Type<span style="color:#eceff4">:</span>     headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">3</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>		Flags<span style="color:#eceff4">:</span>    headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">4</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>		StreamID<span style="color:#eceff4">:</span> binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">5</span><span style="color:#eceff4">:</span><span style="color:#b48ead">9</span><span style="color:#eceff4">])</span> <span style="color:#81a1c1">&amp;</span> <span style="color:#b48ead">0x7FFFFFFF</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Read the payload based on the Length field</span>
</span></span><span style="display:flex;"><span>	payload <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Length<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> header<span style="color:#eceff4">.</span>Length <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		_<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadFull</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">,</span> payload<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> Frame<span style="color:#eceff4">{},</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;reading payload: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> Frame<span style="color:#eceff4">{</span>Header<span style="color:#eceff4">:</span> header<span style="color:#eceff4">,</span> Payload<span style="color:#eceff4">:</span> payload<span style="color:#eceff4">},</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details></p>
]]></content:encoded></item><item><title>IRC Log: rm -rf /var/opt/gitlab/postgresql/data</title><link>https://kmcd.dev/posts/irc-log-rm-rf-/var/opt/gitlab/postgresql/data/</link><pubDate>Mon, 09 Mar 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/irc-log-rm-rf-/var/opt/gitlab/postgresql/data/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/irc-log-rm-rf-/var/opt/gitlab/postgresql/data/cover.svg" /> &lt;/p>
                
                A tired sysadmin accidentally deletes the production database and discovers that all five backup mechanisms have failed.
                </description><content:encoded><![CDATA[<h1 id="the-incident-log-january-31-2017">The Incident Log: January 31, 2017</h1>
<div class="chat-log" style="font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; background-color: #222; color: #eee; padding: 15px; border-radius: 5px; overflow-x: auto; line-height: 1.4; margin-bottom: 20px; font-size: 0.9rem; border: 1px solid #444;">


<div>
  <font color="#777777">[23:00]</font> 
  <font color="#4caf50"><b>*** Topic: DB Replication Lag | Status: 🔴 Critical</b></font>
</div>







<div>
  <font color="#777777">[23:05]</font> 
  
    <font color="#4caf50"><i>*** <font color="#ab47bc"><b>tired_sysadmin</b></font> has joined</i></font>
  
</div>






<div>
  <font color="#777777">[23:10]</font> 
  <font color="#ffa726"><b>&lt;tired_sysadmin&gt;</b></font>
  <span>Replication is stuck again. The secondary node (db2) is refusing to sync.</span>
</div>






<div>
  <font color="#777777">[23:11]</font> 
  <font color="#ffa726"><b>&lt;tired_sysadmin&gt;</b></font>
  <span>I&rsquo;m going to wipe the data directory on db2 and let it pull a fresh copy from master.</span>
</div>






<div>
  <font color="#777777">[23:12]</font> 
  <font color="#ffa726"><b>&lt;tired_sysadmin&gt;</b></font>
  <span>rm -rf /var/opt/gitlab/postgresql/data</span>
</div>






<div>
  <font color="#777777">[23:12]</font> 
  <font color="#ffa726"><b>&lt;tired_sysadmin&gt;</b></font>
  <span>Weird. It&rsquo;s taking a while. Usually empty directories delete instantly.</span>
</div>






<div>
  <font color="#777777">[23:13]</font> 
  <font color="#ef5350"><b>&lt;helper_dev&gt;</b></font>
  <span>Hey, why did the website just go 500?</span>
</div>






<div>
  <font color="#777777">[23:13]</font> 
  <font color="#ffa726"><b>&lt;tired_sysadmin&gt;</b></font>
  <span>&hellip;</span>
</div>






<div>
  <font color="#777777">[23:13]</font> 
  <font color="#ffa726"><b>&lt;tired_sysadmin&gt;</b></font>
  <span>I&rsquo;m looking at my terminal prompt.</span>
</div>






<div>
  <font color="#777777">[23:14]</font> 
  <font color="#ffa726"><b>&lt;tired_sysadmin&gt;</b></font>
  <span>It says root@db1.</span>
</div>






<div>
  <font color="#777777">[23:14]</font> 
  <font color="#ef5350"><b>&lt;helper_dev&gt;</b></font>
  <span>db1 is Prod. You are deleting Prod.</span>
</div>






<div>
  <font color="#777777">[23:15]</font> 
  <font color="#ffa726"><b>&lt;tired_sysadmin&gt;</b></font>
  <span>CTRL+C CTRL+C CTRL+C</span>
</div>






<div>
  <font color="#777777">[23:15]</font> 
  <font color="#ffa726"><b>&lt;tired_sysadmin&gt;</b></font>
  <span>Okay, I stopped it. How much is left?</span>
</div>






<div>
  <font color="#777777">[23:16]</font> 
  <font color="#ef5350"><b>&lt;helper_dev&gt;</b></font>
  <span>Checking&hellip; The directory is 4.5KB.</span>
</div>






<div>
  <font color="#777777">[23:16]</font> 
  <font color="#ffa726"><b>&lt;tired_sysadmin&gt;</b></font>
  <span>We had 300GB of data.</span>
</div>






<div>
  <font color="#777777">[23:17]</font> 
  <font color="#ef5350"><b>&lt;helper_dev&gt;</b></font>
  <span>Okay, don&rsquo;t panic. We have 5 different backup mechanisms. Let&rsquo;s check S3.</span>
</div>






<div>
  <font color="#777777">[23:20]</font> 
  <font color="#ef5350"><b>&lt;helper_dev&gt;</b></font>
  <span>S3 bucket is empty. The backup script has been failing silently since version 8.1.</span>
</div>






<div>
  <font color="#777777">[23:21]</font> 
  <font color="#ffa726"><b>&lt;tired_sysadmin&gt;</b></font>
  <span>Check the Azure disk snapshots.</span>
</div>






<div>
  <font color="#777777">[23:22]</font> 
  <font color="#ef5350"><b>&lt;helper_dev&gt;</b></font>
  <span>Not enabled.</span>
</div>






<div>
  <font color="#777777">[23:23]</font> 
  <font color="#ffa726"><b>&lt;tired_sysadmin&gt;</b></font>
  <span>&hellip;LVM snapshots?</span>
</div>






<div>
  <font color="#777777">[23:24]</font> 
  <font color="#ef5350"><b>&lt;helper_dev&gt;</b></font>
  <span>We take them every 24 hours. We just lost 6 hours of data.</span>
</div>






<div>
  <font color="#777777">[23:25]</font> 
  <font color="#ffa726"><b>&lt;tired_sysadmin&gt;</b></font>
  <span>I am going to live stream the restoration on YouTube so people don&rsquo;t kill us.</span>
</div>


</div>

<p><a href="https://about.gitlab.com/blog/postmortem-of-database-outage-of-january-31/" rel="external">Postmortem of database outage of January 31</a></p>
]]></content:encoded></item><item><title>HTTP/2 From Scratch: Part 3</title><link>https://kmcd.dev/posts/http2-from-scratch-part-3/</link><pubDate>Wed, 04 Mar 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/http2-from-scratch-part-3/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/http2-from-scratch-part-3/cover.svg" /> &lt;/p>
                
                Decoding HPACK and the evolution of the HTTP header
                </description><content:encoded><![CDATA[<p>In the last two posts, we established a raw TCP connection, navigated the TLS handshake with ALPN to select &ldquo;h2&rdquo;, and built a parser that can read the 9-byte frames of an HTTP/2 connection. We have a synchronized, acknowledged connection. Now it&rsquo;s time to do what we came for: request a web page.</p>
<p>This is where HTTP/2 departs dramatically from its predecessor. There is no <code>GET / HTTP/1.1</code>. Instead, we enter the world of compressed headers, pseudo-headers, and stateful tables. This is the world of HPACK: Header Compression for HTTP/2.</p>
<h3 id="what-is-hpack">What is HPACK?</h3>
<p>In HTTP/1.1, headers are human-readable text. This is great for debugging but inefficient. The same headers (like <code>User-Agent</code>) are sent with every single request, wasting bandwidth. <a href="https://www.rfc-editor.org/rfc/rfc7541.html" rel="external">HPACK (RFC 7541)</a> solves this by using several compression strategies. Instead of sending full header names and values, it sends compact, indexed representations.</p>
<p>At its core, HPACK uses two tables to translate between full headers and small integer indices:</p>
<ol>
<li><strong>Static Table</strong>: A predefined, read-only table containing 61 of the most common headers. For example, <code>{':method', 'GET'}</code> is entry #2. Every HTTP/2 client and server knows this table.</li>
<li><strong>Dynamic Table</strong>: A small, temporary table that is specific to a single connection. In a full HPACK implementation, if you send a header that&rsquo;s not in the static table (like a custom <code>x-request-id</code>), it can be added to the dynamic table. On the next request, you can just send its index instead of the full header again. For this part, we will focus solely on the Static Table.</li>
</ol>
<p>Our journey into HPACK will start with the basics. We&rsquo;ll implement a decoder that understands the static table and indexed headers. We&rsquo;ll leave the dynamic table handling for the next article.</p>
<h3 id="decoding-indexed-headers">Decoding Indexed Headers</h3>
<p>The simplest form of header compression is the <strong>Indexed Header Field</strong>. When a header to be sent is present in one of the tables, it can be represented by a single integer.</p>
<p>An indexed header byte starts with a <code>1</code>. The remaining 7 bits are the start of a variable-length integer representing the index in the tables.</p>
<p>Let&rsquo;s look at our <code>hpack.go</code> file. The <code>Decode</code> method reads the payload from a <code>HEADERS</code> frame. If it sees a byte starting with <code>1</code>, it knows it&rsquo;s an indexed header.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>h <span style="color:#81a1c1">*</span>HPACKDecoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Decode</span><span style="color:#eceff4">(</span>payload <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Decoding %d bytes\n&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">)</span> <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		b <span style="color:#81a1c1">:=</span> payload<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span><span style="color:#b48ead">128</span> <span style="color:#81a1c1">==</span> <span style="color:#b48ead">128</span> <span style="color:#eceff4">{</span> <span style="color:#616e87;font-style:italic">// Indexed Header Field (starts with 1)</span>
</span></span><span style="display:flex;"><span>			index<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">,</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> n <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode integer&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			payload <span style="color:#eceff4">=</span> payload<span style="color:#eceff4">[</span>n<span style="color:#eceff4">:]</span>
</span></span><span style="display:flex;"><span>			header<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">(</span>index<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid header index: %d&#34;</span><span style="color:#eceff4">,</span> index<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;  [Header] %s: %s\n&#34;</span><span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#616e87;font-style:italic">// Other header types (like literals) are not implemented yet.</span>
</span></span><span style="display:flex;"><span>			<span style="color:#616e87;font-style:italic">// We&#39;ll tackle this in the next part.</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;not implemented: literal header field&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>The <code>decodeInt</code> function handles HPACK&rsquo;s specific integer encoding. It parses the first byte manually to account for the <code>n</code>-bit prefix. If the integer overflows that first byte, it uses the standard library&rsquo;s <code>binary.Uvarint</code> to efficiently parse the remaining continuation bytes. This hybrid approach correctly handles the HPACK format while leveraging Go&rsquo;s optimized, built-in varint decoder.</p>
<p>With this, our decoder can parse headers that are in the static table. The <code>hpack.go</code> file contains the full static table definition.</p>
<details >
    <summary>
        go/hpack.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-3/go/hpack.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;encoding/binary&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// RFC 7541: HPACK: Header Compression for HTTP/2</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// https://datatracker.ietf.org/doc/html/rfc7541</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">const</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// HPACK Static Table Indices (Masked with 0x80 for Indexed Header Fields)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// See RFC 7541 Appendix A</span>
</span></span><span style="display:flex;"><span>	HpackMethodGet   <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x82</span> <span style="color:#616e87;font-style:italic">// Index 2: :method: GET</span>
</span></span><span style="display:flex;"><span>	HpackPathRoot    <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x84</span> <span style="color:#616e87;font-style:italic">// Index 4: :path: /</span>
</span></span><span style="display:flex;"><span>	HpackSchemeHttps <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x87</span> <span style="color:#616e87;font-style:italic">// Index 7: :scheme: https</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 0x40 is the mask for Literal Header Field with Incremental Indexing</span>
</span></span><span style="display:flex;"><span>	HpackAuthority <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x40</span> <span style="color:#eceff4">|</span> <span style="color:#b48ead">1</span> <span style="color:#616e87;font-style:italic">// Index 1: :authority</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// HeaderField represents a header field with a name and value.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> HeaderField <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Name<span style="color:#eceff4">,</span> Value <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// StaticTable is the predefined, unchangeable table of header fields, as defined in RFC 7541 Appendix A.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> StaticTable <span style="color:#eceff4">=</span> <span style="color:#eceff4">[]</span>HeaderField<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:authority&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:method&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;GET&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:method&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;POST&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:path&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;/&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:path&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;/index.html&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:scheme&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;http&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:scheme&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;200&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;204&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;206&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;304&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;400&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;404&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;500&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-charset&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-encoding&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;gzip, deflate&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-language&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-ranges&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;access-control-allow-origin&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;age&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;allow&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;authorization&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;cache-control&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-disposition&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-encoding&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-language&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-length&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-location&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-range&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-type&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;cookie&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;date&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;etag&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;expect&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;expires&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;from&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;host&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-match&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-modified-since&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-none-match&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-range&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-unmodified-since&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;last-modified&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;link&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;location&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;max-forwards&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;proxy-authenticate&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;proxy-authorization&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;range&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;referer&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;retry-after&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;server&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;set-cookie&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;strict-transport-security&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;transfer-encoding&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;user-agent&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;vary&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;via&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;www-authenticate&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	staticTableMap <span style="color:#eceff4">=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">map</span><span style="color:#eceff4">[</span>HeaderField<span style="color:#eceff4">]</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">init</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> i<span style="color:#eceff4">,</span> hf <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> StaticTable <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		staticTableMap<span style="color:#eceff4">[</span>hf<span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> i <span style="color:#81a1c1">+</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> HPACKDecoder <span style="color:#81a1c1;font-weight:bold">struct</span><span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">NewHPACKDecoder</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">*</span>HPACKDecoder <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>HPACKDecoder<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>h <span style="color:#81a1c1">*</span>HPACKDecoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Header</span><span style="color:#eceff4">(</span>i <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>HeaderField<span style="color:#eceff4">,</span> <span style="color:#81a1c1">bool</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#81a1c1">&lt;=</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> HeaderField<span style="color:#eceff4">{},</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	staticIndex <span style="color:#81a1c1">:=</span> i <span style="color:#81a1c1">-</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> staticIndex <span style="color:#eceff4">&lt;</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>StaticTable<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> StaticTable<span style="color:#eceff4">[</span>staticIndex<span style="color:#eceff4">],</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> HeaderField<span style="color:#eceff4">{},</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>h <span style="color:#81a1c1">*</span>HPACKDecoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Decode</span><span style="color:#eceff4">(</span>payload <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Decoding %d bytes\n&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">)</span> <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		b <span style="color:#81a1c1">:=</span> payload<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span><span style="color:#b48ead">128</span> <span style="color:#81a1c1">==</span> <span style="color:#b48ead">128</span> <span style="color:#eceff4">{</span> <span style="color:#616e87;font-style:italic">// Indexed Header Field</span>
</span></span><span style="display:flex;"><span>			index<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">,</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> n <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode integer&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			payload <span style="color:#eceff4">=</span> payload<span style="color:#eceff4">[</span>n<span style="color:#eceff4">:]</span>
</span></span><span style="display:flex;"><span>			header<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">(</span>index<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid header index: %d&#34;</span><span style="color:#eceff4">,</span> index<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;  [Header] %s: %s\n&#34;</span><span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#616e87;font-style:italic">// Other header field types (literal, etc.) not implemented yet</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;not implemented: literal header field&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// decodeInt decodes a variable-length integer from a byte slice.</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// It returns the decoded integer and the number of bytes consumed.</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// See RFC 7541 section 5.1 for details.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>payload <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> n <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">)</span> <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	mask <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">(</span><span style="color:#b48ead">1</span> <span style="color:#81a1c1">&lt;&lt;</span> n<span style="color:#eceff4">)</span> <span style="color:#81a1c1">-</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	i <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">])</span> <span style="color:#81a1c1">&amp;</span> mask
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#eceff4">&lt;</span> mask <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> i<span style="color:#eceff4">,</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// The value overflows the first byte. The rest of the integer is a</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// standard varint.</span>
</span></span><span style="display:flex;"><span>	val<span style="color:#eceff4">,</span> bytesRead <span style="color:#81a1c1">:=</span> binary<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uvarint</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">:])</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> bytesRead <span style="color:#81a1c1">&lt;=</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span> <span style="color:#616e87;font-style:italic">// Malformed varint</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> i <span style="color:#81a1c1">+</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">(</span>val<span style="color:#eceff4">),</span> <span style="color:#b48ead">1</span> <span style="color:#81a1c1">+</span> bytesRead
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<h3 id="manually-encoding-our-first-request">Manually Encoding Our First Request</h3>
<p>Our current client doesn&rsquo;t have an HPACK <em>encoder</em>. To send our first request, we&rsquo;re going to manually craft the byte payload for our <code>HEADERS</code> frame. This is a great way to understand how encoding works.</p>
<p>We want to send the following headers for a <code>GET /</code> request:</p>
<ul>
<li><code>:method: GET</code></li>
<li><code>:path: /</code></li>
<li><code>:scheme: https</code></li>
<li><code>:authority: kmcd.dev</code></li>
</ul>
<p>Looking at the static table in RFC 7541, Appendix A:</p>
<ul>
<li><code>{':method', 'GET'}</code> is at index 2.</li>
<li><code>{':path', '/'}</code> is at index 4.</li>
<li><code>{':scheme', 'https'}</code> is at index 7.</li>
</ul>
<p>The <code>:authority</code> header doesn&rsquo;t have a perfect match for both name and value. However, its <em>name</em> is at index 1. This means we have to send it as a <strong>Literal Header Field</strong>. This type of header representation has a prefix indicating how it should be handled. For now, we will use &ldquo;Literal Header Field with Incremental Indexing&rdquo; (prefix <code>0100</code>), which tells the server to use this header and add it to the dynamic table. We will do this manually for now, but in the future we will want to create code to manage this for us.</p>
<p>Our <code>client.go</code> assembles this payload:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// content/posts/2026/http2-from-scratch-part-3/go/client.go</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ...</span>
</span></span><span style="display:flex;"><span>	authority <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;kmcd.dev&#34;</span>
</span></span><span style="display:flex;"><span>	requestPayload <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#b48ead">0x82</span><span style="color:#eceff4">,</span> <span style="color:#616e87;font-style:italic">// Index 2: :method: GET</span>
</span></span><span style="display:flex;"><span>		<span style="color:#b48ead">0x84</span><span style="color:#eceff4">,</span> <span style="color:#616e87;font-style:italic">// Index 4: :path: /</span>
</span></span><span style="display:flex;"><span>		<span style="color:#b48ead">0x87</span><span style="color:#eceff4">,</span> <span style="color:#616e87;font-style:italic">// Index 7: :scheme: https</span>
</span></span><span style="display:flex;"><span>		<span style="color:#b48ead">0x41</span><span style="color:#eceff4">,</span> <span style="color:#616e87;font-style:italic">// Index 1 for :authority, with literal value</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>authority<span style="color:#eceff4">)),</span> <span style="color:#616e87;font-style:italic">// Length of &#34;kmcd.dev&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	requestPayload <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>requestPayload<span style="color:#eceff4">,</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>authority<span style="color:#eceff4">)</span><span style="color:#81a1c1">...</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ...</span>
</span></span></code></pre></div><p>Let&rsquo;s break down the bytes:</p>
<ul>
<li><code>0x82</code>: <code>10000010</code>. Starts with <code>1</code>, so it&rsquo;s an indexed header. The integer value is 2. This is <code>:method: GET</code>.</li>
<li><code>0x84</code>: <code>10000100</code>. Indexed header, index 4. This is <code>:path: /</code>.</li>
<li><code>0x87</code>: <code>10000111</code>. Indexed header, index 7. This is <code>:scheme: https</code>.</li>
<li><code>0x41</code>: <code>01000001</code>. Starts with <code>01</code>, so it&rsquo;s a &ldquo;Literal Header Field with Incremental Indexing&rdquo;. The remaining 6 bits are the index for the name, which is 1 (<code>:authority</code>). The value will follow as a literal string.</li>
<li><code>byte(len(authority))</code>: The length of the value &ldquo;kmcd.dev&rdquo;, which is 8.</li>
<li><code>[]byte(authority)</code>: The string &ldquo;kmcd.dev&rdquo; itself.</li>
</ul>
<p>We have now manually encoded a <code>HEADERS</code> payload!</p>
<h3 id="the-clients-structure">The Client&rsquo;s Structure</h3>
<p>Here is a high-level overview of our client&rsquo;s logic so far:</p>
<ol>
<li><strong>Connect &amp; Handshake</strong>: Establish a TCP connection and perform the TLS handshake, using ALPN to negotiate &ldquo;h2&rdquo;.</li>
<li><strong>Send Preface</strong>: Send the magic <code>PRI * ...</code> connection preface.</li>
<li><strong>Exchange Settings</strong>: Send our empty <code>SETTINGS</code> frame, wait for the server&rsquo;s <code>SETTINGS</code> frame, and then send an <code>ACK</code>.</li>
<li><strong>Send Request</strong>: Construct and send the <code>HEADERS</code> frame for <code>GET /</code> with our manually encoded HPACK payload. We set the <code>END_STREAM</code> flag to indicate this is our entire request.</li>
<li><strong>Read Response</strong>: Loop and read frames from the server.
<ul>
<li>If it&rsquo;s a <code>HEADERS</code> frame, use our <code>HPACKDecoder</code> to parse and print the response headers.</li>
<li>If it&rsquo;s a <code>DATA</code> frame, print the content.</li>
<li>If we see a frame with the <code>END_STREAM</code> flag, the response is complete, and we exit.</li>
</ul>
</li>
</ol>
<p>This gives us a working end-to-end client that makes a real HTTP/2 request and prints the response.</p>
<h3 id="putting-it-all-together-the-final-client">Putting It All Together: The Final Client</h3>
<p>Here is the full <code>client.go</code> script.</p>
<details >
    <summary>
        go/client.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-3/go/client.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;crypto/tls&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;encoding/binary&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">const</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Protocol constants</span>
</span></span><span style="display:flex;"><span>	Preface <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n&#34;</span>
</span></span><span style="display:flex;"><span>	Server  <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;kmcd.dev:443&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Frame Types (RFC 9113 Section 6)</span>
</span></span><span style="display:flex;"><span>	FrameData         <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x0</span>
</span></span><span style="display:flex;"><span>	FrameHeaders      <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x1</span>
</span></span><span style="display:flex;"><span>	FramePriority     <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x2</span>
</span></span><span style="display:flex;"><span>	FrameRstStream    <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x3</span>
</span></span><span style="display:flex;"><span>	FrameSettings     <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x4</span>
</span></span><span style="display:flex;"><span>	FramePushPromise  <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x5</span>
</span></span><span style="display:flex;"><span>	FramePing         <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x6</span>
</span></span><span style="display:flex;"><span>	FrameGoAway       <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x7</span>
</span></span><span style="display:flex;"><span>	FrameWindowUpdate <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x8</span>
</span></span><span style="display:flex;"><span>	FrameContinuation <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x9</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Common Flags</span>
</span></span><span style="display:flex;"><span>	FlagAck        <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x01</span> <span style="color:#616e87;font-style:italic">// For SETTINGS/PING</span>
</span></span><span style="display:flex;"><span>	FlagEndStream  <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x01</span> <span style="color:#616e87;font-style:italic">// For DATA/HEADERS</span>
</span></span><span style="display:flex;"><span>	FlagEndHeaders <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x04</span> <span style="color:#616e87;font-style:italic">// For HEADERS/PUSH_PROMISE/CONTINUATION</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 1. Setup TLS with ALPN</span>
</span></span><span style="display:flex;"><span>	config <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>tls<span style="color:#eceff4">.</span>Config<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		NextProtos<span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#34;h2&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	conn<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> tls<span style="color:#eceff4">.</span><span style="color:#88c0d0">Dial</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;tcp&#34;</span><span style="color:#eceff4">,</span> Server<span style="color:#eceff4">,</span> config<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to connect: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	state <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">ConnectionState</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#34;h2&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Server did not negotiate HTTP/2: %s&#34;</span><span style="color:#eceff4">,</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Connected to %s using %s\n&#34;</span><span style="color:#eceff4">,</span> Server<span style="color:#eceff4">,</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 2. Send Connection Preface</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>Preface<span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to send preface: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Preface sent.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 3. Initial Handshake Loop</span>
</span></span><span style="display:flex;"><span>	hpackDec <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">NewHPACKDecoder</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Send our initial empty settings</span>
</span></span><span style="display:flex;"><span>	mySettings <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> FrameSettings<span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>mySettings<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	serverSettingsAcked <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>	mySettingsAcked <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">!</span>serverSettingsAcked <span style="color:#81a1c1">||</span> <span style="color:#eceff4">!</span>mySettingsAcked <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		frame<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">ReadFrame</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Handshake read error: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; [Handshake] Frame Type=%d, Flags=%d, Stream=%d\n&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>StreamID<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">switch</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameSettings<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#81a1c1">&amp;</span>FlagAck <span style="color:#81a1c1">!=</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				mySettingsAcked <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>				fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; Server ACK&#39;d our settings&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				ack <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> FrameSettings<span style="color:#eceff4">,</span> FlagAck<span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>				conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>ack<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>				serverSettingsAcked <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>				fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&gt;&gt;&gt; Sent SETTINGS ACK&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameWindowUpdate<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; Server provided flow control window&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameGoAway<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Server sent GOAWAY during handshake&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 4. Send the Request (GET /)</span>
</span></span><span style="display:flex;"><span>	authority <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;kmcd.dev&#34;</span>
</span></span><span style="display:flex;"><span>	requestPayload <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		HpackMethodGet<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		HpackPathRoot<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		HpackSchemeHttps<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		HpackAuthority<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>authority<span style="color:#eceff4">)),</span> <span style="color:#616e87;font-style:italic">// The length prefix for the string literal</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	requestPayload <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>requestPayload<span style="color:#eceff4">,</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>authority<span style="color:#eceff4">)</span><span style="color:#81a1c1">...</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	header <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">9</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	payloadLen <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>requestPayload<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>payloadLen <span style="color:#81a1c1">&gt;&gt;</span> <span style="color:#b48ead">16</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>payloadLen <span style="color:#81a1c1">&gt;&gt;</span> <span style="color:#b48ead">8</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">2</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>payloadLen<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">3</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> FrameHeaders
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">4</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> FlagEndStream <span style="color:#eceff4">|</span> FlagEndHeaders
</span></span><span style="display:flex;"><span>	binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">PutUint32</span><span style="color:#eceff4">(</span>header<span style="color:#eceff4">[</span><span style="color:#b48ead">5</span><span style="color:#eceff4">:</span><span style="color:#b48ead">9</span><span style="color:#eceff4">],</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>header<span style="color:#eceff4">,</span> requestPayload<span style="color:#81a1c1">...</span><span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to send request: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&gt;&gt;&gt; Sent HEADERS (Stream 1)&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 5. Main Processing Loop</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		frame<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">ReadFrame</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Connection closed: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; Frame Type=%d, Flags=%d, Stream=%d\n&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>StreamID<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">switch</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameData<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;      [DATA] %s\n&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameHeaders<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;      [HEADERS] Decoding...&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> hpackDec<span style="color:#eceff4">.</span><span style="color:#88c0d0">Decode</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;HPACK Error: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameGoAway<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			lastStream <span style="color:#81a1c1">:=</span> binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uint32</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">:</span><span style="color:#b48ead">4</span><span style="color:#eceff4">])</span> <span style="color:#81a1c1">&amp;</span> <span style="color:#b48ead">0x7FFFFFFF</span>
</span></span><span style="display:flex;"><span>			errCode <span style="color:#81a1c1">:=</span> binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uint32</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">[</span><span style="color:#b48ead">4</span><span style="color:#eceff4">:</span><span style="color:#b48ead">8</span><span style="color:#eceff4">])</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;!!! GOAWAY: Last Stream %d, Error Code %d\n&#34;</span><span style="color:#eceff4">,</span> lastStream<span style="color:#eceff4">,</span> errCode<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameWindowUpdate<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;      [WINDOW_UPDATE]&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Use the global constants for the exit condition</span>
</span></span><span style="display:flex;"><span>		isEndFrame <span style="color:#81a1c1">:=</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#81a1c1">==</span> FrameData <span style="color:#81a1c1">||</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#81a1c1">==</span> FrameHeaders
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> isEndFrame <span style="color:#81a1c1">&amp;&amp;</span> <span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#81a1c1">&amp;</span>FlagEndStream <span style="color:#81a1c1">!=</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Stream finished. Exiting.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<p>Running this client produces a full HTTP/2 interaction. We send our request and get back headers and data from the server, all parsed by our own code. Here&rsquo;s what it looks like when we run it (body truncated to reduce noise):</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go run .
</span></span><span style="display:flex;"><span>Connected to kmcd.dev:443 using h2
</span></span><span style="display:flex;"><span>Preface sent.
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">&lt;&lt;&lt;</span> <span style="color:#81a1c1">[</span>Handshake<span style="color:#81a1c1">]</span> Frame Type<span style="color:#81a1c1">=</span>4, Flags<span style="color:#81a1c1">=</span>0, Stream<span style="color:#81a1c1">=</span><span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>&gt;&gt;&gt; Sent SETTINGS ACK
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">&lt;&lt;&lt;</span> <span style="color:#81a1c1">[</span>Handshake<span style="color:#81a1c1">]</span> Frame Type<span style="color:#81a1c1">=</span>8, Flags<span style="color:#81a1c1">=</span>0, Stream<span style="color:#81a1c1">=</span><span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">&lt;&lt;&lt;</span> Server provided flow control window
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">&lt;&lt;&lt;</span> <span style="color:#81a1c1">[</span>Handshake<span style="color:#81a1c1">]</span> Frame Type<span style="color:#81a1c1">=</span>4, Flags<span style="color:#81a1c1">=</span>1, Stream<span style="color:#81a1c1">=</span><span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">&lt;&lt;&lt;</span> Server ACK<span style="color:#bf616a">&#39;</span>d our settings
</span></span><span style="display:flex;"><span>&gt;&gt;&gt; Sent HEADERS <span style="color:#81a1c1">(</span>Stream 1<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">&lt;&lt;&lt;</span> Frame Type<span style="color:#81a1c1">=</span>1, Flags<span style="color:#81a1c1">=</span>4, Stream<span style="color:#81a1c1">=</span><span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">[</span>HEADERS<span style="color:#81a1c1">]</span> Decoding...
</span></span><span style="display:flex;"><span>Decoding <span style="color:#b48ead">705</span> bytes
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">[</span>Header<span style="color:#81a1c1">]</span> :status: <span style="color:#b48ead">200</span>
</span></span><span style="display:flex;"><span>2026/01/31 14:36:00 HPACK Error: not implemented: literal header field
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">&lt;&lt;&lt;</span> Frame Type<span style="color:#81a1c1">=</span>0, Flags<span style="color:#81a1c1">=</span>0, Stream<span style="color:#81a1c1">=</span><span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">[</span>DATA<span style="color:#81a1c1">]</span> &lt;!doctype html&gt;&lt;html lang<span style="color:#81a1c1">=</span>en&gt;<span style="color:#81a1c1">[</span>...snipped...<span style="color:#81a1c1">]</span>&lt;/html&gt;
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">&lt;&lt;&lt;</span> Frame Type<span style="color:#81a1c1">=</span>0, Flags<span style="color:#81a1c1">=</span>1, Stream<span style="color:#81a1c1">=</span><span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">[</span>DATA<span style="color:#81a1c1">]</span>
</span></span></code></pre></div><p>I want you to notice a few things here. We successfully make it through the initial handshake. The server sends us headers and we decode only a single header, the <code>:status</code> pseudo-header that tells us that the response is a <code>200</code> but the next header doesn&rsquo;t exist in the status table and is sent as a string literal and since our code doesn&rsquo;t yet handle string literals we have to stop processing the headers at this point. This will be improved later. Finally, I want you to notice that we have successfully received the DATA frame. We actually receive two of them: one that contains the data and another that says that we have received all of the data for the request. This is a significant improvement. We are <strong>very close</strong> to a fully functioning client!</p>
<h3 id="whats-next">What&rsquo;s Next?</h3>
<p>We&rsquo;ve made a huge leap. We can now make requests and parse simple responses. However, as you just saw, our HPACK decoder is incomplete. In this part, we&rsquo;ve focused on decoding <strong>Indexed Header Fields</strong> that refer exclusively to the <strong>Static Table</strong>. This means our client will successfully decode common headers like <code>:method: GET</code> or <code>:path: /</code> but will completely fail on any string literal or any reference to the <strong>Dynamic Table</strong>.</p>
<p>In the next and final part covering HTTP/2, we will complete our HPACK implementation and adapt our client to use the <code>http.Request</code> and <code>http.Response</code> types.</p>
<p>See all of the code mentioned in this article here:
<details >
    <summary>
        go/client.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-3/go/client.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;crypto/tls&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;encoding/binary&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">const</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Protocol constants</span>
</span></span><span style="display:flex;"><span>	Preface <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n&#34;</span>
</span></span><span style="display:flex;"><span>	Server  <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;kmcd.dev:443&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Frame Types (RFC 9113 Section 6)</span>
</span></span><span style="display:flex;"><span>	FrameData         <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x0</span>
</span></span><span style="display:flex;"><span>	FrameHeaders      <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x1</span>
</span></span><span style="display:flex;"><span>	FramePriority     <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x2</span>
</span></span><span style="display:flex;"><span>	FrameRstStream    <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x3</span>
</span></span><span style="display:flex;"><span>	FrameSettings     <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x4</span>
</span></span><span style="display:flex;"><span>	FramePushPromise  <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x5</span>
</span></span><span style="display:flex;"><span>	FramePing         <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x6</span>
</span></span><span style="display:flex;"><span>	FrameGoAway       <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x7</span>
</span></span><span style="display:flex;"><span>	FrameWindowUpdate <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x8</span>
</span></span><span style="display:flex;"><span>	FrameContinuation <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x9</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Common Flags</span>
</span></span><span style="display:flex;"><span>	FlagAck        <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x01</span> <span style="color:#616e87;font-style:italic">// For SETTINGS/PING</span>
</span></span><span style="display:flex;"><span>	FlagEndStream  <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x01</span> <span style="color:#616e87;font-style:italic">// For DATA/HEADERS</span>
</span></span><span style="display:flex;"><span>	FlagEndHeaders <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x04</span> <span style="color:#616e87;font-style:italic">// For HEADERS/PUSH_PROMISE/CONTINUATION</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 1. Setup TLS with ALPN</span>
</span></span><span style="display:flex;"><span>	config <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>tls<span style="color:#eceff4">.</span>Config<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		NextProtos<span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#34;h2&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	conn<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> tls<span style="color:#eceff4">.</span><span style="color:#88c0d0">Dial</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;tcp&#34;</span><span style="color:#eceff4">,</span> Server<span style="color:#eceff4">,</span> config<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to connect: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	state <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">ConnectionState</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#34;h2&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Server did not negotiate HTTP/2: %s&#34;</span><span style="color:#eceff4">,</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Connected to %s using %s\n&#34;</span><span style="color:#eceff4">,</span> Server<span style="color:#eceff4">,</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 2. Send Connection Preface</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>Preface<span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to send preface: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Preface sent.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 3. Initial Handshake Loop</span>
</span></span><span style="display:flex;"><span>	hpackDec <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">NewHPACKDecoder</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Send our initial empty settings</span>
</span></span><span style="display:flex;"><span>	mySettings <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> FrameSettings<span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>mySettings<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	serverSettingsAcked <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>	mySettingsAcked <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">!</span>serverSettingsAcked <span style="color:#81a1c1">||</span> <span style="color:#eceff4">!</span>mySettingsAcked <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		frame<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">ReadFrame</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Handshake read error: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; [Handshake] Frame Type=%d, Flags=%d, Stream=%d\n&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>StreamID<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">switch</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameSettings<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#81a1c1">&amp;</span>FlagAck <span style="color:#81a1c1">!=</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				mySettingsAcked <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>				fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; Server ACK&#39;d our settings&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				ack <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> FrameSettings<span style="color:#eceff4">,</span> FlagAck<span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>				conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>ack<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>				serverSettingsAcked <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>				fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&gt;&gt;&gt; Sent SETTINGS ACK&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameWindowUpdate<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; Server provided flow control window&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameGoAway<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Server sent GOAWAY during handshake&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 4. Send the Request (GET /)</span>
</span></span><span style="display:flex;"><span>	authority <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;kmcd.dev&#34;</span>
</span></span><span style="display:flex;"><span>	requestPayload <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		HpackMethodGet<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		HpackPathRoot<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		HpackSchemeHttps<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		HpackAuthority<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>authority<span style="color:#eceff4">)),</span> <span style="color:#616e87;font-style:italic">// The length prefix for the string literal</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	requestPayload <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>requestPayload<span style="color:#eceff4">,</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>authority<span style="color:#eceff4">)</span><span style="color:#81a1c1">...</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	header <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">9</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	payloadLen <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>requestPayload<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>payloadLen <span style="color:#81a1c1">&gt;&gt;</span> <span style="color:#b48ead">16</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>payloadLen <span style="color:#81a1c1">&gt;&gt;</span> <span style="color:#b48ead">8</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">2</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>payloadLen<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">3</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> FrameHeaders
</span></span><span style="display:flex;"><span>	header<span style="color:#eceff4">[</span><span style="color:#b48ead">4</span><span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> FlagEndStream <span style="color:#eceff4">|</span> FlagEndHeaders
</span></span><span style="display:flex;"><span>	binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">PutUint32</span><span style="color:#eceff4">(</span>header<span style="color:#eceff4">[</span><span style="color:#b48ead">5</span><span style="color:#eceff4">:</span><span style="color:#b48ead">9</span><span style="color:#eceff4">],</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>header<span style="color:#eceff4">,</span> requestPayload<span style="color:#81a1c1">...</span><span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to send request: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&gt;&gt;&gt; Sent HEADERS (Stream 1)&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 5. Main Processing Loop</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		frame<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">ReadFrame</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Connection closed: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;&lt;&lt;&lt; Frame Type=%d, Flags=%d, Stream=%d\n&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>StreamID<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">switch</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameData<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;      [DATA] %s\n&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameHeaders<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;      [HEADERS] Decoding...&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> hpackDec<span style="color:#eceff4">.</span><span style="color:#88c0d0">Decode</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;HPACK Error: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameGoAway<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			lastStream <span style="color:#81a1c1">:=</span> binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uint32</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">:</span><span style="color:#b48ead">4</span><span style="color:#eceff4">])</span> <span style="color:#81a1c1">&amp;</span> <span style="color:#b48ead">0x7FFFFFFF</span>
</span></span><span style="display:flex;"><span>			errCode <span style="color:#81a1c1">:=</span> binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uint32</span><span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Payload<span style="color:#eceff4">[</span><span style="color:#b48ead">4</span><span style="color:#eceff4">:</span><span style="color:#b48ead">8</span><span style="color:#eceff4">])</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;!!! GOAWAY: Last Stream %d, Error Code %d\n&#34;</span><span style="color:#eceff4">,</span> lastStream<span style="color:#eceff4">,</span> errCode<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> FrameWindowUpdate<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;      [WINDOW_UPDATE]&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Use the global constants for the exit condition</span>
</span></span><span style="display:flex;"><span>		isEndFrame <span style="color:#81a1c1">:=</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#81a1c1">==</span> FrameData <span style="color:#81a1c1">||</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#81a1c1">==</span> FrameHeaders
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> isEndFrame <span style="color:#81a1c1">&amp;&amp;</span> <span style="color:#eceff4">(</span>frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Flags<span style="color:#81a1c1">&amp;</span>FlagEndStream <span style="color:#81a1c1">!=</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Stream finished. Exiting.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<details >
    <summary>
        go/hpack.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-3/go/hpack.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;encoding/binary&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// RFC 7541: HPACK: Header Compression for HTTP/2</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// https://datatracker.ietf.org/doc/html/rfc7541</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">const</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// HPACK Static Table Indices (Masked with 0x80 for Indexed Header Fields)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// See RFC 7541 Appendix A</span>
</span></span><span style="display:flex;"><span>	HpackMethodGet   <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x82</span> <span style="color:#616e87;font-style:italic">// Index 2: :method: GET</span>
</span></span><span style="display:flex;"><span>	HpackPathRoot    <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x84</span> <span style="color:#616e87;font-style:italic">// Index 4: :path: /</span>
</span></span><span style="display:flex;"><span>	HpackSchemeHttps <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x87</span> <span style="color:#616e87;font-style:italic">// Index 7: :scheme: https</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 0x40 is the mask for Literal Header Field with Incremental Indexing</span>
</span></span><span style="display:flex;"><span>	HpackAuthority <span style="color:#81a1c1">uint8</span> <span style="color:#eceff4">=</span> <span style="color:#b48ead">0x40</span> <span style="color:#eceff4">|</span> <span style="color:#b48ead">1</span> <span style="color:#616e87;font-style:italic">// Index 1: :authority</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// HeaderField represents a header field with a name and value.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> HeaderField <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Name<span style="color:#eceff4">,</span> Value <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// StaticTable is the predefined, unchangeable table of header fields, as defined in RFC 7541 Appendix A.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> StaticTable <span style="color:#eceff4">=</span> <span style="color:#eceff4">[]</span>HeaderField<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:authority&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:method&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;GET&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:method&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;POST&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:path&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;/&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:path&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;/index.html&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:scheme&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;http&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:scheme&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;200&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;204&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;206&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;304&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;400&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;404&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;:status&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;500&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-charset&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-encoding&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;gzip, deflate&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-language&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept-ranges&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;accept&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;access-control-allow-origin&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;age&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;allow&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;authorization&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;cache-control&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-disposition&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-encoding&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-language&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-length&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-location&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-range&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;content-type&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;cookie&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;date&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;etag&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;expect&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;expires&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;from&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;host&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-match&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-modified-since&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-none-match&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-range&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;if-unmodified-since&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;last-modified&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;link&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;location&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;max-forwards&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;proxy-authenticate&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;proxy-authorization&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;range&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;referer&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;retry-after&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;server&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;set-cookie&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;strict-transport-security&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;transfer-encoding&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;user-agent&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;vary&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;via&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;www-authenticate&#34;</span><span style="color:#eceff4">,</span> Value<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	staticTableMap <span style="color:#eceff4">=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">map</span><span style="color:#eceff4">[</span>HeaderField<span style="color:#eceff4">]</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">init</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> i<span style="color:#eceff4">,</span> hf <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> StaticTable <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		staticTableMap<span style="color:#eceff4">[</span>hf<span style="color:#eceff4">]</span> <span style="color:#eceff4">=</span> i <span style="color:#81a1c1">+</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> HPACKDecoder <span style="color:#81a1c1;font-weight:bold">struct</span><span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">NewHPACKDecoder</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">*</span>HPACKDecoder <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>HPACKDecoder<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>h <span style="color:#81a1c1">*</span>HPACKDecoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Header</span><span style="color:#eceff4">(</span>i <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>HeaderField<span style="color:#eceff4">,</span> <span style="color:#81a1c1">bool</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#81a1c1">&lt;=</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> HeaderField<span style="color:#eceff4">{},</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	staticIndex <span style="color:#81a1c1">:=</span> i <span style="color:#81a1c1">-</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> staticIndex <span style="color:#eceff4">&lt;</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>StaticTable<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> StaticTable<span style="color:#eceff4">[</span>staticIndex<span style="color:#eceff4">],</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> HeaderField<span style="color:#eceff4">{},</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>h <span style="color:#81a1c1">*</span>HPACKDecoder<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Decode</span><span style="color:#eceff4">(</span>payload <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Decoding %d bytes\n&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">)</span> <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		b <span style="color:#81a1c1">:=</span> payload<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> b<span style="color:#81a1c1">&amp;</span><span style="color:#b48ead">128</span> <span style="color:#81a1c1">==</span> <span style="color:#b48ead">128</span> <span style="color:#eceff4">{</span> <span style="color:#616e87;font-style:italic">// Indexed Header Field</span>
</span></span><span style="display:flex;"><span>			index<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">,</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> n <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode integer&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			payload <span style="color:#eceff4">=</span> payload<span style="color:#eceff4">[</span>n<span style="color:#eceff4">:]</span>
</span></span><span style="display:flex;"><span>			header<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> h<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">(</span>index<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid header index: %d&#34;</span><span style="color:#eceff4">,</span> index<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;  [Header] %s: %s\n&#34;</span><span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Value<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#616e87;font-style:italic">// Other header field types (literal, etc.) not implemented yet</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;not implemented: literal header field&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// decodeInt decodes a variable-length integer from a byte slice.</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// It returns the decoded integer and the number of bytes consumed.</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// See RFC 7541 section 5.1 for details.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">decodeInt</span><span style="color:#eceff4">(</span>payload <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> n <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">)</span> <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	mask <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">(</span><span style="color:#b48ead">1</span> <span style="color:#81a1c1">&lt;&lt;</span> n<span style="color:#eceff4">)</span> <span style="color:#81a1c1">-</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	i <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">])</span> <span style="color:#81a1c1">&amp;</span> mask
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#eceff4">&lt;</span> mask <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> i<span style="color:#eceff4">,</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// The value overflows the first byte. The rest of the integer is a</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// standard varint.</span>
</span></span><span style="display:flex;"><span>	val<span style="color:#eceff4">,</span> bytesRead <span style="color:#81a1c1">:=</span> binary<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uvarint</span><span style="color:#eceff4">(</span>payload<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">:])</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> bytesRead <span style="color:#81a1c1">&lt;=</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span> <span style="color:#616e87;font-style:italic">// Malformed varint</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> i <span style="color:#81a1c1">+</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">(</span>val<span style="color:#eceff4">),</span> <span style="color:#b48ead">1</span> <span style="color:#81a1c1">+</span> bytesRead
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<details >
    <summary>
        go/parser.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-3/go/parser.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;encoding/binary&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// FrameHeader represents the 9-byte fixed header of every HTTP/2 frame.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> FrameHeader <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Length   <span style="color:#81a1c1">uint32</span>
</span></span><span style="display:flex;"><span>	Type     <span style="color:#81a1c1">uint8</span>
</span></span><span style="display:flex;"><span>	Flags    <span style="color:#81a1c1">uint8</span>
</span></span><span style="display:flex;"><span>	StreamID <span style="color:#81a1c1">uint32</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Frame represents a complete HTTP/2 frame including its payload.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> Frame <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Header  FrameHeader
</span></span><span style="display:flex;"><span>	Payload <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ReadFrame reads a header and then the corresponding payload from the connection.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">ReadFrame</span><span style="color:#eceff4">(</span>r io<span style="color:#eceff4">.</span>Reader<span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>Frame<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 1. Read the 9-byte header</span>
</span></span><span style="display:flex;"><span>	headerBuf <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">9</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadFull</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">,</span> headerBuf<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> Frame<span style="color:#eceff4">{},</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;reading header: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 2. Parse the header fields using bit-shifting</span>
</span></span><span style="display:flex;"><span>	header <span style="color:#81a1c1">:=</span> FrameHeader<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Length<span style="color:#eceff4">:</span>   <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">])</span><span style="color:#81a1c1">&lt;&lt;</span><span style="color:#b48ead">16</span> <span style="color:#eceff4">|</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">])</span><span style="color:#81a1c1">&lt;&lt;</span><span style="color:#b48ead">8</span> <span style="color:#eceff4">|</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">2</span><span style="color:#eceff4">]),</span>
</span></span><span style="display:flex;"><span>		Type<span style="color:#eceff4">:</span>     headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">3</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>		Flags<span style="color:#eceff4">:</span>    headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">4</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>		StreamID<span style="color:#eceff4">:</span> binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">5</span><span style="color:#eceff4">:</span><span style="color:#b48ead">9</span><span style="color:#eceff4">])</span> <span style="color:#81a1c1">&amp;</span> <span style="color:#b48ead">0x7FFFFFFF</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 3. Read the payload based on the Length field</span>
</span></span><span style="display:flex;"><span>	payload <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Length<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> header<span style="color:#eceff4">.</span>Length <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		_<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadFull</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">,</span> payload<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> Frame<span style="color:#eceff4">{},</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;reading payload: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> Frame<span style="color:#eceff4">{</span>Header<span style="color:#eceff4">:</span> header<span style="color:#eceff4">,</span> Payload<span style="color:#eceff4">:</span> payload<span style="color:#eceff4">},</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details></p>
]]></content:encoded></item><item><title>Building a Live BGP Map</title><link>https://kmcd.dev/posts/live-internet-map/</link><pubDate>Mon, 02 Mar 2026 08:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/live-internet-map/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/live-internet-map/map_hu_cad483650bdd0548.webp" /> &lt;/p>
                
                Building a cool looking, real-time BGP map
                </description><content:encoded><![CDATA[<p>Right now, thousands of routers are arguing about how to reach each other. That’s expected. It’s how the Internet works. This website wouldn&rsquo;t load without it. BGP (Border Gateway Protocol) continuously announces and withdraws prefixes, adjusting how traffic moves globally. Most people see URLs and apps; routers see prefixes and AS paths.</p>
<p>I made a map that lets us listen in on this conversation, but in a relaxing, aesthetically pleasing way.</p>
<p>In my <a href="https://kmcd.dev/posts/internet-map-2026/">last post</a>, I mentioned a <a href="https://ris-live.ripe.net/" rel="external">websocket-based streaming API from RIPE</a>. At the time, I set it aside. Soon, it became my obsession and the live view was born. While this visualization occasionally stumbles into being practically useful for spotting global outages, my primary requirement was simply to build a really cool looking map.</p>
<p>You can check out the source code for this project on <a href="https://github.com/sudorandom/bgp-stream/" rel="external">GitHub</a> or watch the map in action on my <a href="http://livemap.kmcd.dev" rel="external">YouTube channel</a> or here:</p>
<div class="video-container" style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
    <iframe 
        src="https://www.youtube.com/embed/live_stream?channel=UCA9eO4Gt-Ua6lAEGzWQHQFA" 
        style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" 
        frameborder="0"
        referrerpolicy="strict-origin-when-cross-origin"
        allowfullscreen>
    </iframe>
</div>

<h2 id="what-are-we-looking-at">What are we looking at?</h2>
<p>This map is a live visualization of the Border Gateway Protocol (BGP). This is the &ldquo;language&rdquo; routers use to talk to each other and decide the best path for your data to travel across the globe.</p>
<p>Imagine a router trying to find the best way to send traffic to Google. It receives multiple path advertisements from its neighbors, and it has to pick the most efficient route:</p>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/c04fb5bc870aa8a2f50b6180ea0ba9f184fcf27c8067271b61a925fb25774bbc.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<p>Every pulse on the map represents a real routing update. Sometimes it’s routine churn. Sometimes it’s maintenance, an outage, or a path change somewhere along the way.</p>
<h3 id="the-global-game-of-telephone">The Global Game of Telephone</h3>
<p>To understand why the map pulses, you have to look at how routers talk. BGP is a <strong>path-vector protocol</strong>, which is effectively a global game of telephone. When a network (an Autonomous System, or AS) wants to be found, it tells its immediate neighbors, who tell their neighbors, and so on.</p>
<ul>
<li><strong>The Announcement:</strong> When a router in Tokyo says, &ldquo;I have a path to <code>8.8.8.0/24</code>,&rdquo; it sends an <strong>Update</strong> to its peers. Every peer that hears this stamps the message with its own ID before passing it along. This list of stamps is called the <strong>AS Path</strong>.</li>
<li><strong>The Selection:</strong> Routers generally prefer the shortest path. If an observer in New York hears the same news from London (2 hops) and Sydney (5 hops), it will automatically choose the shorter London route. On the live map, you will see this selection light up as a purple pulse.</li>
<li><strong>The Withdrawal:</strong> If a fiber line is cut, for example, the router sends a <strong>Withdrawal</strong>. This is where the game of telephone gets frantic. Neighbors start checking their old notes: <em>&ldquo;Wait, I can&rsquo;t go through London anymore? What about that longer path through Sydney I heard about earlier?&rdquo;</em></li>
</ul>
<p>Here is how that &ldquo;envelope&rdquo; looks as it travels from Tokyo to New York. Notice how the path grows longer at every step.</p>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/9d2c25efa6219537763b7acde7ce28d0eda34ca0adafd7e0aeadd45985af0341.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<p>Because routers often wait a few seconds before passing news along (to avoid &ldquo;vibrating&rdquo; the whole internet with every tiny hiccup), these updates arrive in waves. On the live map, this looks like a ripple of activity starting at the origin and washing over the globe as the &ldquo;signatures&rdquo; accumulate.</p>
<h3 id="spotting-a-bgp-flap">Spotting a BGP Flap</h3>
<p>If you are watching the map and suddenly see a wave of pulses lighting up all over the world at the exact same time, you might be witnessing a BGP flap.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">
<figure><a href="https://kmcd.dev/posts/live-internet-map/flappy-bird.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
                
            
            
        
    

    
    
    
        
        
        
            
        
    

    
    
        
    

    <img src="https://kmcd.dev/posts/live-internet-map/flappy-bird_hu_239242fbe15406ed.png" width="200" height="105" loading="lazy"/>
    </a>
</figure>


    </span>

    
</div>

<p>In networking, flapping happens when a route rapidly appears and disappears. Imagine a misconfigured router or a loose fiber cable. The router yells to the Internet, &ldquo;I have a path to Google!&rdquo; only to drop the connection a second later and say, &ldquo;Never mind, it is gone&rdquo;. That single localized hiccup doesn’t stay local. It ripples outward as routers everywhere recalculate their paths. To keep the whole system from grinding to a halt, modern routers use Route Flap Damping. This essentially puts the noisy network in a time-out until it proves it can stay stable.</p>
<h3 id="decoding-the-pulses">Decoding the Pulses</h3>
<p>When you see those colored pulses popping off on the map, they represent BGP updates processed through a multi-stage classification engine. Rather than just showing raw protocol messages (which is what the earlier version of the map did), the map categorizes events into four distinct colors based on their behavior and potential impact.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">
<figure><a href="https://kmcd.dev/posts/live-internet-map/legend.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/live-internet-map/legend_hu_940e5d1180e3cbdd.png"
         alt="Legend" loading="lazy"/>
    </a>
</figure>


    </span>

    
</div>

<h4 id="anomalies-and-behaviors">Anomalies and Behaviors</h4>
<p>The classification engine also maps events into Level 2 categorizations (anomalies) based on heuristics applied over recent activity windows. To make sense of the noise, the multi-stage engine uses specific triggers to drop these events into the four colored buckets before presenting them on the map. These fall into four severity tiers:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Severity Tier</th>
          <th style="text-align: left">Color</th>
          <th style="text-align: left">Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><strong>Critical</strong></td>
          <td style="text-align: left">Red</td>
          <td style="text-align: left">Significant routing failures, such as a prefix sustaining multiple withdrawals with no announcements, or path violations that suggest a route leak.</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Bad</strong></td>
          <td style="text-align: left">Orange</td>
          <td style="text-align: left">Highly volatile or inefficient behavior, including rapid &ldquo;flapping&rdquo; of routes, excessive &ldquo;babbling&rdquo; (a term I coined for this project) with unchanged attributes, or frequent next-hop changes.</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Normal / Policy</strong></td>
          <td style="text-align: left">Purple</td>
          <td style="text-align: left">Standard routing adjustments, such as traffic engineering (Policy Churn), path length oscillations, or the natural &ldquo;Path Hunting&rdquo; process where routers explore alternatives during convergence.</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Normal / Discovery</strong></td>
          <td style="text-align: left">Blue</td>
          <td style="text-align: left">Routine background noise, including standard prefix origination or redundant gossip pulses that keep routing tables current.</td>
      </tr>
  </tbody>
</table>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">
<figure><a href="https://kmcd.dev/posts/live-internet-map/map-animation-noui.webp" class="spotlight" data-download="true">
    
    
    
    
        
            
            
        
    

    
    
    
        
        
        
    

    
    
        
    

    <img src="https://kmcd.dev/posts/live-internet-map/map-animation-noui.webp" width="700"/>
    </a>
</figure>


    </span>

    
</div>

<p>When you zoom out and see all those colors firing at once, the true scale of the Internet comes to life. It tells the story of over 70,000 independent networks coordinating in real time.</p>
<h2 id="what-else-is-on-the-map">What else is on the map?</h2>
<p>To make sure the map isn&rsquo;t just a wall of moving dots, I included several dashboard elements that provide context to the chaos:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Preview</th>
          <th style="text-align: left">Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><figure><a href="https://kmcd.dev/posts/live-internet-map/active-countries.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/live-internet-map/active-countries_hu_842f4e66236d4c60.png"
         alt="Active Countries" loading="lazy"/>
    </a>
</figure>
</td>
          <td style="text-align: left">Ranks the countries currently experiencing the highest volume of network updates. It is an instant look at where in the world the most &ldquo;routing churn&rdquo; is happening at any given moment.</td>
      </tr>
      <tr>
          <td style="text-align: left"><figure><a href="https://kmcd.dev/posts/live-internet-map/bgp-anomalies.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/live-internet-map/bgp-anomalies_hu_23c349819fba7568.png"
         alt="BGP Anomalies" loading="lazy"/>
    </a>
</figure>
</td>
          <td style="text-align: left">Tracks the specific network blocks that have the worst anomalies that we&rsquo;ve detected. Great for spotting outages or a flapping link. Another name for this is the networking &ldquo;wall of shame&rdquo;.</td>
      </tr>
      <tr>
          <td style="text-align: left"><figure><a href="https://kmcd.dev/posts/live-internet-map/activity-trend.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/live-internet-map/activity-trend_hu_a24d13fd3953c4eb.png"
         alt="Activity Trend" loading="lazy"/>
    </a>
</figure>
</td>
          <td style="text-align: left">A rolling 60-second activity graph. It tracks whether activity is spiking or calming down, letting you see the difference between routine background noise and a massive routing event.</td>
      </tr>
      <tr>
          <td style="text-align: left"><figure><a href="https://kmcd.dev/posts/live-internet-map/beacon-analysis.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/live-internet-map/beacon-analysis_hu_fcf5a679764f5ad0.png"
         alt="Beacon Analysis" loading="lazy"/>
    </a>
</figure>
</td>
          <td style="text-align: left">A dynamic donut chart separating &ldquo;Organic&rdquo; traffic from &ldquo;Beacons&rdquo; (special test signals sent out by researchers). It helps show how much activity is natural versus intentional measurement. More on this below.</td>
      </tr>
      <tr>
          <td style="text-align: left"><figure><a href="https://kmcd.dev/posts/live-internet-map/now-playing.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/live-internet-map/now-playing_hu_b0e7f4030245a3e6.png"
         alt="Now Playing" loading="lazy"/>
    </a>
</figure>
</td>
          <td style="text-align: left">The current background music track.</td>
      </tr>
  </tbody>
</table>
<h3 id="path-hunting-and-anycast">Path Hunting and Anycast</h3>
<p>When I first started watching the live data, I was confused by why a single localized outage would trigger a massive global explosion of pulses.</p>
<p>I&rsquo;ve since learned this is likely due to a phenomenon called <a href="https://www.noction.com/blog/bgp-path-hunting" rel="external">&ldquo;Path Hunting.&rdquo;</a> When a route dies, the Internet doesn&rsquo;t instantly agree it&rsquo;s gone. Instead, routers desperately try to find backup paths. They&rsquo;ll try a longer route, fail, try an even longer one, fail again, and generate a new BGP update every single time. Those massive bursts of purple pulses are basically the routers &ldquo;thinking out loud&rdquo; as they scramble to route around the damage.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">

    
    
        
        
        <img src="https://kmcd.dev/posts/live-internet-map/asn32934.gif"/>
    



    </span>

    
        <figcaption><a href="https://blog.cloudflare.com/october-2021-facebook-outage/" rel="external">Routers looking for Facebook&rsquo;s network on October 4, 2021</a></figcaption>
    
</div>

<p>This scramble to find backup paths can occasionally leave behind an interesting anomaly known as a &ldquo;BGP zombie.&rdquo; If a router fails to process a withdrawal message due to a software bug or slow propagation, it will stubbornly keep announcing a dead path to its neighbors, creating a localized black hole for traffic. <a href="https://blog.cloudflare.com/going-bgp-zombie-hunting/" rel="external">Cloudflare has a great write-up</a> on hunting down these undead routes if you want to fall down that rabbit hole.</p>
<p><strong>Anycast</strong> routing amplifies this chatter even further. Huge networks (like Google or Cloudflare) announce the exact same <code>/24</code> prefix from dozens of different physical locations globally so their services are fast everywhere. But if a major transit provider drops a peering session, or a provider intentionally shifts traffic away from a datacenter for maintenance, thousands of routers might suddenly decide to shift their traffic to a different Anycast node all at once. The result is a sudden surge of routing adjustments across the map.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/live-internet-map/spiderman-meme_hu_30e697353dd306c1.webp"/>
    



    </span>

    
</div>

<h3 id="ripe-ris-beacons-and-anchors">RIPE RIS Beacons and Anchors</h3>
<p>While building the &ldquo;Most Active Prefixes&rdquo; list, I kept noticing the exact same thing: <code>/24</code> subnets were overrepresented on the leaderboard.</p>
<p>A <code>/24</code> (256 IPs) is effectively the smallest globally routable unit, so most churn naturally happens at that granularity.</p>
<p>But there was another reason for seeing the <em>same</em> /24 subnets appearing on the list. Not all activity on the map comes from failing links or organic traffic shifts. There is also intentional &lsquo;breakage&rsquo; happening behind the scenes to test BGP propagation.</p>
<p>It turns out RIPE RIS operates <a href="https://ris.ripe.net/docs/routing-beacons/" rel="external">Routing Beacons</a>. Routing Beacons are prefixes deliberately announced and withdrawn on a fixed schedule, typically every two hours. One of them announces and withdraws every <em>10 minutes</em>. Researchers use these beacons as a controlled signal inside the global routing table to study BGP propagation and convergence. To make the activity list useful, I had to write logic to classify and filter these beacons out of the ranking.</p>
<p>RIPE also runs &ldquo;Anchors&rdquo; alongside these beacons. While a beacon prefix constantly flips on and off, an anchor is a prefix permanently announced from the exact same physical router. This gives researchers a stable control group. They can compare the volatile beacon traffic against a baseline of stable routing from the identical location.</p>
<p>I eventually added a Beacon Analysis view that separates &ldquo;organic&rdquo; updates from beacon-driven ones. It makes the metrics more accurate and highlights how much traffic is from deliberate live validation.</p>
<h3 id="bgp-babbling-and-attribute-churn">BGP Babbling and Attribute Churn</h3>
<p>So if a burst of updates isn&rsquo;t a dying link, a desperate search for a backup path, or a research beacon, what else could it be? Sometimes a network is just fidgeting. I call this <strong>babbling</strong>. While not an official industry term, it perfectly describes the constant, repetitive &ldquo;talk&rdquo; of updates that don&rsquo;t actually change anything meaningful about the route.</p>
<p>I caught a great example of this while watching the stream. A Finnish fiber provider (<code>AS43016</code>) was firing off nearly 100 pulses per second, and this went on for days. The raw data showed the route wasn&rsquo;t actually dropping. Instead, a single piece of metadata called the Aggregator ID just kept flipping back and forth.</p>
<p>This creates a localized flurry of activity. Some router somewhere was probably misconfigured and couldn’t make up its mind about how to summarize its own network. Every time it changed its mind, even by a single bit, it had to update every other router on Earth. Standard monitoring tools usually miss these &ldquo;attribute flaps&rdquo; because the network stays perfectly reachable. But on the map, they paint a very clear picture: a constant, rhythmic heartbeat of orange &ldquo;bad behavior&rdquo; pulses.</p>
<p>I built <a href="https://github.com/sudorandom/bgp-stream/blob/main/cmd/debug-prefix/main.go" rel="external">a tool</a> to debug noisy prefixes like this. It aggregates BGP update stats and tries to diagnose the root cause, such as path oscillation, a flapping link, or heavy Anycast routing. Here is the output for our problem child over at <code>AS43016</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ just debug-prefix 195.155.146.0/24
</span></span><span style="display:flex;"><span>BGP Prefix Monitor Stats <span style="color:#81a1c1">(</span>Running <span style="color:#81a1c1;font-weight:bold">for</span> 293.4s<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>--------------------------------------------------
</span></span><span style="display:flex;"><span>Announcements: <span style="color:#b48ead">4576</span> <span style="color:#81a1c1">(</span>15.60/s<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>Withdrawals:   <span style="color:#b48ead">1422</span> <span style="color:#81a1c1">(</span>4.85/s<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>Total Msgs:    <span style="color:#b48ead">5101</span> <span style="color:#81a1c1">(</span>17.39/s<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>Unique Peers:  <span style="color:#b48ead">310</span>
</span></span><span style="display:flex;"><span>--------------------------------------------------
</span></span><span style="display:flex;"><span>GLOBAL CHURN EVENTS:
</span></span><span style="display:flex;"><span>  AS-Path Changes:  <span style="color:#b48ead">2275</span>
</span></span><span style="display:flex;"><span>  Community Changes: <span style="color:#b48ead">3259</span>
</span></span><span style="display:flex;"><span>  Next-Hop Changes:  <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Aggregator Flaps:  <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Path Length Flaps: <span style="color:#b48ead">1255</span>
</span></span><span style="display:flex;"><span>--------------------------------------------------
</span></span><span style="display:flex;"><span>LIKELY CONCLUSIONS:
</span></span><span style="display:flex;"><span>  - Path Length Oscillation <span style="color:#81a1c1">(</span>Route is toggling between different path lengths<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>  - BGP Babbling <span style="color:#81a1c1">(</span>Excessive update rate detected<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>--------------------------------------------------
</span></span><span style="display:flex;"><span>Top <span style="color:#b48ead">5</span> Churning Peers:
</span></span><span style="display:flex;"><span>  187.16.220.216: <span style="color:#b48ead">149</span> attribute changes
</span></span><span style="display:flex;"><span>  5.188.4.211: <span style="color:#b48ead">142</span> attribute changes
</span></span><span style="display:flex;"><span>  103.152.35.254: <span style="color:#b48ead">142</span> attribute changes
</span></span><span style="display:flex;"><span>  177.221.140.2: <span style="color:#b48ead">138</span> attribute changes
</span></span><span style="display:flex;"><span>  154.18.4.110: <span style="color:#b48ead">132</span> attribute changes
</span></span></code></pre></div><p>At the time of publishing, this prefix is still babbling away. This script became the basis for the classification engine that I discuss later on in the article.</p>
<hr>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/live-internet-map/spiderman-meme-2_hu_9976c7f4b0045332.webp"/>
    



    </span>

    
</div>

<h2 id="making-the-map">Making the map</h2>
<p>Handling 30,000+ BGP updates per second takes more than plotting points on a canvas. The project is written in Go for its concurrency model and relies on Ebitengine for hardware-accelerated 2D rendering.</p>
<h3 id="why-a-stream">Why a Stream?</h3>
<p>I originally planned to build this as a standard web frontend, similar to my <a href="https://kmcd.dev" rel="external">previous map</a>. However, I hit two massive walls almost immediately.</p>
<p>The first problem was the sheer volume of data. BGP updates can easily peak at over 30,000 events per second. Forcing a web browser to process that firehose while maintaining a smooth 30 FPS with complex blending is just not in the cards today.</p>
<p>The second problem was scaling. If the map actually got popular, having thousands of browsers opening individual websocket connections to the RIPE RIS-Live service would be a disaster. It is wildly inefficient, and accidentally DDoSing a service designed to monitor Internet stability was not on my to-do list.</p>
<p>Here is what that scenario looks like:</p>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/4ecdb9458c887ddd9e314374eb921d66df420008975c778224044d8b959053ad.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<p>To protect the RIPE service from being overwhelmed, the logical next step was to put a middleman in place to handle the multiplexing. This led me to a standard client-server architecture:</p>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/99c474a1d9f432e74133869ad09bd956f33183249fc7687f28cbda094c816e6c.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<p>Multiplexing solves the connection problem, but it completely ignores the browser rendering issues I was having. To guarantee a smooth 30 FPS for everyone without melting their CPUs, I decided to bypass the browser canvas entirely. I pivoted the architecture to a centralized YouTube stream:</p>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/95c23f24e899f3d83f1a102135a6f7fc67cdeadf07064bc158c5a3a763454b3b.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<p>Now I had a choice. Scenario 1 was dead on arrival because it could make the operators of RIPE RIS-Live <em>very sad</em> and potentially angry. That left me with the choice between building a complex backend service to multiplex that single RIPE connection to all my users (Scenario 2), or completely changing how people view the map by streaming to YouTube (Scenario 3). I went with the latter option.</p>
<p>Rendering the entire visualization on my own server and broadcasting it guarantees that every viewer gets the exact same high-fidelity experience, regardless of their hardware. It is easy to run on a TV where the browser version isn&rsquo;t really viable. This pivot also made the tech stack an obvious choice. Once I started experimenting with <a href="https://ebitengine.org/" rel="external">Ebitengine</a>, hardware-accelerated rendering in Go gave me crisper, far more fluid visuals than I could ever squeeze out of a standard browser canvas.</p>
<p>The downside is reduced interaction: no zooming, no toggling UI, no customization. I think this tradeoff was ultimately worth it, but I just want to note what I lost from making this dramatic change in architecture.</p>
<h3 id="flattening-ip-space">Flattening IP Space</h3>
<p>To map a BGP update to a geographic location, you need reliable IP-to-region data. I am currently only focusing on IPv4, and that data comes from five Regional Internet Registries (RIRs). Each registry publishes large and sometimes overlapping delegated stats files.</p>
<p>Fragmented lookups across raw datasets might be fine for offline processing, but we have a strict frame rate budget. If the engine had to search through five separate datasets for every single update, the visualization would stutter. At 30,000+ updates per second, efficiency is pretty important.</p>
<p>To solve this, I preprocess all the data upfront using a sweep-line algorithm. Each IP range acts as a segment on a 1D number line. The algorithm walks across this space, resolves any overlaps between registries, and collapses millions of ranges into a single, clean, non-overlapping index.</p>
<p>For example, take two overlapping registry entries:</p>
<ul>
<li><strong>Range A (ARIN):</strong> <code>10.0.0.0</code> to <code>10.0.0.255</code></li>
<li><strong>Range B (RIPE):</strong> <code>10.0.0.128</code> to <code>10.0.1.255</code></li>
</ul>
<p>The algorithm flattens these into three distinct, non-overlapping segments:</p>
<ol>
<li><code>10.0.0.0</code> to <code>10.0.0.127</code> (ARIN only)</li>
<li><code>10.0.0.128</code> to <code>10.0.0.255</code> (Conflict resolved)</li>
<li><code>10.0.1.0</code> to <code>10.0.1.255</code> (RIPE only)</li>
</ol>
<p>This preprocessing seems like overkill, but it&rsquo;s worth it since it makes lookups super cheap. I back this index with BadgerDB and a <a href="https://github.com/sudorandom/bgp-stream/blob/main/pkg/utils/disk_trie.go" rel="external">DiskTrie for high-performance persistent storage</a>. This allows the engine to track &ldquo;seen&rdquo; prefixes seamlessly across different sessions without eating up memory.</p>
<h3 id="managing-the-firehose">Managing the Firehose</h3>
<p>BGP updates arrive continuously, and during route flapping events the volume spikes hard.</p>
<p>To keep the visualization readable without becoming an incomprehensible mess, the pipeline waits 10 seconds to ensure a withdrawal isn&rsquo;t just a rapid path re-convergence, and paces the visual output so spikes are emitted smoothly every 500ms.</p>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/67272ed234f06451a6efb0cc5fda29f46a4670751478997a278e0fc1a0d90b04.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<h2 id="aesthetics-and-motion">Aesthetics and Motion</h2>
<p>Animations use interpolation instead of snapping to the next state. For parts of the map which update infrequently, I wanted to highlight that a change occurred. For that, I added a &ldquo;glitch&rdquo; effect to the &ldquo;Top Activity Hubs&rdquo; and &ldquo;Most Active Prefixes&rdquo; to make it more obvious and to add to the cyberpunk aesthetic. These effects add polish, but too much motion detracts from the vibes of the map. Finding that balance took restraint and a surprisingly large amount of experimentation.</p>
<p>The pulses are what actually bring the data to life. In the engine, each pulse is a simple generated glow texture. I add a bit of spatial jitter so concurrent events do not stack perfectly on top of each other, and I scale their sizes logarithmically so massive data spikes do not turn the map into a solid wall of color.</p>
<p>The colors map directly to the severity tiers: red for critical events, orange for bad behavior, purple for policy churn and hunting, and blue for routine discovery. Because they use additive blending, overlapping pulses naturally create a bright hotspot over regions with a ton of routing activity. They pop onto the map, expand, and fade out smoothly. Managing this entire visual lifecycle efficiently is what keeps the map feeling dynamic without tanking the frame rate.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">
<figure><a href="https://kmcd.dev/posts/live-internet-map/europe-animation.webp" class="spotlight" data-download="true">
    
    
    
    
        
            
            
        
    

    
    
    
        
        
        
    

    
    
        
    

    <img src="https://kmcd.dev/posts/live-internet-map/europe-animation.webp"
         alt="Animation of BGP events in Europe" width="700"/>
    </a><figcaption>
            <p>Animation of BGP events in Europe</p>
        </figcaption>
</figure>


    </span>

    
</div>

<h4 id="the-mollweide-projection">The Mollweide Projection</h4>
<p>Mercator would have been easy, but it heavily distorts size near the poles. For a global activity map, that felt misleading.</p>
<p>I chose the <a href="https://en.wikipedia.org/wiki/Mollweide_projection" rel="external">Mollweide projection</a>.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">
<figure><a href="https://kmcd.dev/posts/live-internet-map/mollweide.svg" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
        
    

    
    
    
        
        
        
    

    
    
        
    

    <img src="https://kmcd.dev/posts/live-internet-map/mollweide.svg" width="700"/>
    </a>
</figure>


    </span>

    
        <figcaption><a href="https://commons.wikimedia.org/w/index.php?curid=66467569" rel="external">By Justin Kunimune - Own work, CC BY-SA 4.0</a></figcaption>
    
</div>

<p>This is an equal-area projection, which means it accurately represents the physical footprint of different regions. It produces a world view that still feels familiar without exaggerating high-latitude areas.</p>
<h2 id="more-meaningful-events">More Meaningful Events</h2>
<p>Raw BGP messages only tell us two things: a route was announced, or a route was withdrawn. So how does the dashboard know when to declare a &rsquo;link flap&rsquo;, a &lsquo;route leak&rsquo;, or a massive &lsquo;outage&rsquo;? The short answer is that I built a classification engine that takes the pattern of raw announce/withdrawal updates that BGP provides and converts them more meaningful events. Some kinds of events are easier to detect than others.</p>
<p>Route leaks are a great example of how messy this can get. Initially, I tried to validate routes using databases like <a href="https://blog.cloudflare.com/rpki/" rel="external">Cloudflare&rsquo;s RPKI portal</a>, specifically hooking into their <code>rpki.json</code> endpoint. The goal was to check if the announcements for networks actually matched their registered ASNs. In practice, this resulted in way too many false positives because a massive number of announcements just were not matching the registered ASNs. If I had kept that logic, the map would have been permanently covered in red alert pulses.</p>
<p>Because of the noise, I ended up implementing a check for the valley-free routing principle. To understand why this works, we have to look at how BGP treats business relationships. BGP routing policies are built around who is paying whom. A network typically has providers it pays for transit, customers who pay it for access, and peers it swaps traffic with for mutual benefit. The valley-free rule dictates that a network should never act as a free transit bridge between two of its providers or peers.</p>
<p>Imagine a small regional network buys internet access from both AT&amp;T and Verizon for redundancy. AT&amp;T shares its global routing table with this small network so it knows where to send data. If that small network accidentally announces all of those AT&amp;T routes to Verizon, it is inadvertently telling the entire internet to send all traffic between Verizon and AT&amp;T through its local routers. Traffic would flow down from Verizon, into the small regional network, and back up to AT&amp;T. That &ldquo;down and back up&rdquo; path is what creates the valley shape in the AS path. Because that small network does not have the capacity to handle global Tier-1 traffic, it immediately gets crushed under the weight of the data. The network drops packets and causes a massive localized internet outage, which is a classic route leak.</p>
<p><div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/52bb994d833891862a19de27eaec72f6afba518eff7b2e57d99cdd1a297d4b7d.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/d19283b128fb32018d06fe2c536377582bc916e310f685b9081b35c5bb58cf14.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div></p>
<p>So, when the classification engine sees an AS path that violates this principle by dipping down into a lower-tier network and back up to a major provider, the system flags it as a route leak. While it serves as a decent baseline, I am generally uncertain about relying solely on this method. It is definitely a part of the classification engine that I want to explore and refine over time.</p>
<div class="warning-box">
  Note that since I published this post, I significantly changed how route leaks are detected. Yes, it does involve the valley-free routing principle <em>and</em> RPKI, but the story is a bit more complex and will probably require an entire post to explain. More on this topic in the future!
</div>

<p>Other rules are slightly more straightforward:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Event</th>
          <th style="text-align: left">Detection Trigger</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><strong>Outage</strong></td>
          <td style="text-align: left">&gt;= 3 withdrawals, 0 announcements</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Route Leak</strong></td>
          <td style="text-align: left">path contains Tier-1 to non-Tier-1 to Tier-1</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Link Flap</strong></td>
          <td style="text-align: left">&gt; 5 withdrawals, announce:withdrawal ratio &lt; 2.5</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Babbling</strong></td>
          <td style="text-align: left">High volume, unchanged attributes</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Next-Hop Flap</strong></td>
          <td style="text-align: left">&gt;= 5 next-hop changes, stable path length</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Aggregator Flap</strong></td>
          <td style="text-align: left">&gt; 10 AGGREGATOR changes</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Policy Churn</strong></td>
          <td style="text-align: left">Elevated attribute changes</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Path Oscillation</strong></td>
          <td style="text-align: left">Frequent path length switching</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Path Hunting</strong></td>
          <td style="text-align: left">Increasing path length, then withdrawal</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Discovery</strong></td>
          <td style="text-align: left">Prolonged announcements, few changes</td>
      </tr>
  </tbody>
</table>
<p>To make these determinations, the engine analyzes the last five minutes of event data. Once classified, a prefix holds its state for ten minutes before being reevaluated. This means a prefix might eventually downgrade from a <code>Link Flap</code> to a routine <code>Discovery</code>. <code>Outage</code> states are the sole exception and clear immediately upon any new announcement. These initial rules are just a baseline I plan to refine as the project evolves.</p>
<hr>
<p>Here is the final result, which I&rsquo;ve gazed at for far too long already:</p>
<a href="http://livemap.kmcd.dev" target="_blank">
<div class="diagram-wrapper">
    <span class="diagram"
        style="">
<figure><a href="https://kmcd.dev/posts/live-internet-map/map-animation.webp" class="spotlight" data-download="true">
    
    
    
    
        
            
            
        
    

    
    
    
        
        
        
    

    
    
        
    

    <img src="https://kmcd.dev/posts/live-internet-map/map-animation.webp"
         alt="🔴 livemap.kmcd.dev" width="600"/>
    </a><figcaption>
            <p>🔴 <a href="http://livemap.kmcd.dev" rel="external">livemap.kmcd.dev</a></p>
        </figcaption>
</figure>


    </span>

    
</div>

</a>

<p>This project turned into a deeper dive into BGP than I expected. Watching as routing updates happen live exposes patterns that are impossible to find with a static snapshot. It has been a rewarding project and I am extremely happy with the result.</p>
<p>So please, toss the live stream on your TV, sit back, relax, and watch the Internet route the world&rsquo;s network traffic as you listen to relaxing lofi in the background.</p>
]]></content:encoded></item><item><title>HTTP/2 From Scratch: Part 2</title><link>https://kmcd.dev/posts/http2-from-scratch-part-2/</link><pubDate>Wed, 25 Feb 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/http2-from-scratch-part-2/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/http2-from-scratch-part-2/cover.svg" /> &lt;/p>
                
                Diving into the binary framing layer and byte-shifting in Go
                </description><content:encoded><![CDATA[<p>In the previous post, we successfully performed the TLS handshake and sent our 24-byte connection preface. To the server, we now look like a valid HTTP/2 client. But as soon as that preface is sent, the server starts talking back in a language we haven&rsquo;t yet taught our Go code to understand.</p>
<p>While HTTP/1.1 communicated in lines of text separated by newlines, HTTP/2 communicates in <strong>frames</strong>. Every single interaction from here on out (settings, headers, data, and keepalive pings) happens inside of a frame. To move us closer to our goal of understanding HTTP/2, we need to build a parser that can slice into these binary packets and make sense of the bits inside.</p>
<h3 id="the-anatomy-of-a-frame">The Anatomy of a Frame</h3>
<p>According to the specification, every HTTP/2 frame begins with a fixed 9-byte header. This header is the roadmap for the rest of the payload. It tells us exactly how much data follows and how we should interpret it.</p>
<p>The 9-byte header is laid out precisely. Using a packet visualization, we can see how the bits are packed together:</p>
<div class="container">
  <pre class="mermaid">packet-beta
0-23: "Length (24 bits)"
24-31: "Type (8 bits)"
32-39: "Flags (8 bits)"
40: "R (Reserved)"
41-71: "Stream Identifier (31 bits)"
  </pre>
</div>
<p>The header fields serve specific purposes:</p>
<ul>
<li><strong>Length:</strong> The size of the frame payload (up to 16,383 bytes by default).</li>
<li><strong>Type:</strong> The specific purpose of the frame (e.g., <code>0x0</code> for DATA, <code>0x1</code> for HEADERS).</li>
<li><strong>Flags:</strong> Boolean modifiers that change how the frame is processed.</li>
<li><strong>Stream Identifier:</strong> A unique ID that links the frame to a specific request-response lifecycle.</li>
</ul>
<p>The first bit of the Stream Identifier is reserved (R) and must remain zero. You will see this a lot in binary protocols, where bits are reserved for the potential of making a backwards compatible next version of the protocol or byte alignment.</p>
<h3 id="modeling-the-header-in-go">Modeling the Header in Go</h3>
<p>To handle this in Go, we need a structure that mirrors the spec. While the length and stream ID are 24 and 31 bits respectively, we use <code>uint32</code> to store them and handle the bit-masking manually during the read.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> FrameHeader <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    Length   <span style="color:#81a1c1">uint32</span>
</span></span><span style="display:flex;"><span>    Type     <span style="color:#81a1c1">uint8</span>
</span></span><span style="display:flex;"><span>    Flags    <span style="color:#81a1c1">uint8</span>
</span></span><span style="display:flex;"><span>    StreamID <span style="color:#81a1c1">uint32</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>The challenge here is that Go&rsquo;s <code>encoding/binary</code> package doesn&rsquo;t have a direct way to read a 24-bit integer. We have to read three bytes and manually shift them into place. We are no longer working with strings; we are working with bitwise operators.</p>
<h3 id="thinking-in-bits">Thinking in Bits</h3>
<p>Before we write the parser, we need to get comfortable with Bit Shifting. In Go, we use the <code>&lt;&lt;</code> (left shift) and <code>|</code> (bitwise OR) operators to &ldquo;stitch&rdquo; separate bytes into a single number.</p>
<p>Think of it like a conveyor belt. To turn three separate bytes into one 24-bit integer, we take the first byte and slide it 16 places to the left, move the second byte 8 places to the left, and then lay them all on top of each other using the OR operator.</p>
<p>In Go, <code>uint32(data[0]) &lt;&lt; 16</code> tells the compiler to take an 8-bit byte, treat it as a 32-bit number, and move its value to the &ldquo;high&rdquo; end of the bucket. <code>HTTP/2</code> always uses network byte order (big-endian). Go’s <code>binary.BigEndian</code> makes this explicit when reconstructing integers.</p>
<p>Bitwise operations take some getting used to, so it’s worth spending time with them if this feels unfamiliar. I recommend <a href="https://www.practical-go-lessons.com/chap-27-enum-iota-and-bitmask" rel="external">Chapter 27: Enum, Iota &amp; Bitmask</a> from <a href="https://www.practical-go-lessons.com/" rel="external">Practical Go Lessons</a>.</p>
<h3 id="parsing-the-bytes">Parsing the Bytes</h3>
<p>When the server responds, we read the first 9 bytes from our <code>tls.Conn</code>. We then use bit-shifting to reconstruct the fields. For the Stream Identifier, we use <code>binary.BigEndian.Uint32</code> and then mask out the first bit.</p>
<p>We also need to handle the <strong>Reserved (R)</strong> bit in the Stream Identifier. Even though we read 4 bytes (32 bits), the spec says the first bit must be ignored. We use the bitwise AND operator with <code>0x7FFFFFFF</code>, which is a binary 0 followed by thirty-one 1s—to &ldquo;mask out&rdquo; that first bit and ensure our ID is always interpreted correctly.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">ReadFrameHeader</span><span style="color:#eceff4">(</span>data <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> FrameHeader <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// Length is 24 bits: first 3 bytes shifted and OR&#39;d</span>
</span></span><span style="display:flex;"><span>    length <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>data<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">])</span><span style="color:#81a1c1">&lt;&lt;</span><span style="color:#b48ead">16</span> <span style="color:#eceff4">|</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>data<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">])</span><span style="color:#81a1c1">&lt;&lt;</span><span style="color:#b48ead">8</span> <span style="color:#eceff4">|</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>data<span style="color:#eceff4">[</span><span style="color:#b48ead">2</span><span style="color:#eceff4">])</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// Type and Flags are single bytes</span>
</span></span><span style="display:flex;"><span>    fType <span style="color:#81a1c1">:=</span> data<span style="color:#eceff4">[</span><span style="color:#b48ead">3</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>    flags <span style="color:#81a1c1">:=</span> data<span style="color:#eceff4">[</span><span style="color:#b48ead">4</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// Stream ID is 31 bits. We read 4 bytes starting at offset 5, </span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// then mask out the reserved bit (0x7FFFFFFF)</span>
</span></span><span style="display:flex;"><span>    streamID <span style="color:#81a1c1">:=</span> binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uint32</span><span style="color:#eceff4">(</span>data<span style="color:#eceff4">[</span><span style="color:#b48ead">5</span><span style="color:#eceff4">:</span><span style="color:#b48ead">9</span><span style="color:#eceff4">])</span> <span style="color:#81a1c1">&amp;</span> <span style="color:#b48ead">0x7FFFFFFF</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">return</span> FrameHeader<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        Length<span style="color:#eceff4">:</span>   length<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        Type<span style="color:#eceff4">:</span>     fType<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        Flags<span style="color:#eceff4">:</span>    flags<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        StreamID<span style="color:#eceff4">:</span> streamID<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><h3 id="the-settings-frame">The SETTINGS Frame</h3>
<p>Immediately after receiving our preface, the server will send a <code>SETTINGS</code> frame (Type <code>0x04</code>). This is the protocol&rsquo;s way of establishing the rules of the road. It defines limits like how many concurrent streams we can open or how large our header table can be.</p>
<p>The payload of a SETTINGS frame consists of a series of 6-byte parameters:</p>
<div class="container">
  <pre class="mermaid">packet-beta
title SETTINGS Parameter (6 Bytes)
0-15: "Identifier (16 bits)"
16-47: "Value (32 bits)"
  </pre>
</div>
<p>For now, the most important thing is that we simply <strong>acknowledge</strong> these settings. HTTP/2 requires that when you receive a SETTINGS frame, the client must send back a SETTINGS frame with the <code>ACK</code> flag set (bit <code>0x1</code>). If you don&rsquo;t, the server will eventually assume the client is unresponsive and close the connection.</p>
<h3 id="putting-it-into-practice">Putting it into Practice</h3>
<p>We can now update our client to read the header, read the payload based on the length we just parsed, and log what we found. This is where the binary nature of the protocol becomes visible in your terminal.</p>
<details open>
    <summary>
        full parser.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-2/go/parser.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;encoding/binary&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// FrameHeader represents the 9-byte fixed header of every HTTP/2 frame.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> FrameHeader <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Length   <span style="color:#81a1c1">uint32</span>
</span></span><span style="display:flex;"><span>	Type     <span style="color:#81a1c1">uint8</span>
</span></span><span style="display:flex;"><span>	Flags    <span style="color:#81a1c1">uint8</span>
</span></span><span style="display:flex;"><span>	StreamID <span style="color:#81a1c1">uint32</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Frame represents a complete HTTP/2 frame including its payload.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> Frame <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Header  FrameHeader
</span></span><span style="display:flex;"><span>	Payload <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ReadFrame reads a header and then the corresponding payload from the connection.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">ReadFrame</span><span style="color:#eceff4">(</span>r io<span style="color:#eceff4">.</span>Reader<span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>Frame<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 1. Read the 9-byte header</span>
</span></span><span style="display:flex;"><span>	headerBuf <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">9</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadFull</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">,</span> headerBuf<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> Frame<span style="color:#eceff4">{},</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;reading header: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 2. Parse the header fields using bit-shifting</span>
</span></span><span style="display:flex;"><span>	header <span style="color:#81a1c1">:=</span> FrameHeader<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Length<span style="color:#eceff4">:</span>   <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">])</span><span style="color:#81a1c1">&lt;&lt;</span><span style="color:#b48ead">16</span> <span style="color:#eceff4">|</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">])</span><span style="color:#81a1c1">&lt;&lt;</span><span style="color:#b48ead">8</span> <span style="color:#eceff4">|</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">2</span><span style="color:#eceff4">]),</span>
</span></span><span style="display:flex;"><span>		Type<span style="color:#eceff4">:</span>     headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">3</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>		Flags<span style="color:#eceff4">:</span>    headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">4</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>		StreamID<span style="color:#eceff4">:</span> binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">5</span><span style="color:#eceff4">:</span><span style="color:#b48ead">9</span><span style="color:#eceff4">])</span> <span style="color:#81a1c1">&amp;</span> <span style="color:#b48ead">0x7FFFFFFF</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 3. Read the payload based on the Length field</span>
</span></span><span style="display:flex;"><span>	payload <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Length<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> header<span style="color:#eceff4">.</span>Length <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		_<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadFull</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">,</span> payload<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> Frame<span style="color:#eceff4">{},</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;reading payload: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> Frame<span style="color:#eceff4">{</span>Header<span style="color:#eceff4">:</span> header<span style="color:#eceff4">,</span> Payload<span style="color:#eceff4">:</span> payload<span style="color:#eceff4">},</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<p>Running this against <code>kmcd.dev</code> will show you exactly what Cloudflare or your server expects. You&rsquo;ll see a <code>Type: 4</code> frame with a specific length. This is the first time our code is truly listening to an HTTP/2 server.</p>
<h3 id="whats-next">What’s Next?</h3>
<p>We can now read the frame header and the raw payload, but the raw payload represents a HEADERS frame and we have no idea how to actually parse that yet. In the next post, we will tackle the most complex part of the HTTP/2 specification: <strong>HPACK</strong>.</p>
<p>We need to learn how to compress and decompress headers using a stateful table, which is the only way we will ever be able to send a real <code>GET</code> request and get a real response.</p>
<p>See all of the code mentioned in this article here:
<details >
    <summary>
        go/client.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-2/go/client.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;crypto/tls&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">const</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// The &#34;Magic&#34; preface required by RFC 9113</span>
</span></span><span style="display:flex;"><span>	preface <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n&#34;</span>
</span></span><span style="display:flex;"><span>	server  <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;kmcd.dev:443&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// We configure TLS to specifically look for &#34;h2&#34; via ALPN</span>
</span></span><span style="display:flex;"><span>	config <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>tls<span style="color:#eceff4">.</span>Config<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		NextProtos<span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#34;h2&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Dial the server and perform the handshake</span>
</span></span><span style="display:flex;"><span>	conn<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> tls<span style="color:#eceff4">.</span><span style="color:#88c0d0">Dial</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;tcp&#34;</span><span style="color:#eceff4">,</span> server<span style="color:#eceff4">,</span> config<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to connect: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Ensure the server actually agreed to speak HTTP/2</span>
</span></span><span style="display:flex;"><span>	state <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">ConnectionState</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#34;h2&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Server did not negotiate HTTP/2: %s&#34;</span><span style="color:#eceff4">,</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Connected to %s using %s\n&#34;</span><span style="color:#eceff4">,</span> server<span style="color:#eceff4">,</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Send the Connection Preface to initialize the H2 session</span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>preface<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to send preface: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Preface sent successfully. The connection is open.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	frame<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">ReadFrame</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to read server settings: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Check the Type on the Header field of our Frame struct</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type <span style="color:#81a1c1">!=</span> <span style="color:#b48ead">0x04</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Expected SETTINGS frame (0x04), got: %d&#34;</span><span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Type<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Received SETTINGS: %d bytes on Stream %d\n&#34;</span><span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>Length<span style="color:#eceff4">,</span> frame<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span>StreamID<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 2. Acknowledge the settings frame</span>
</span></span><span style="display:flex;"><span>	ackHeader <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#b48ead">0x00</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0x00</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0x00</span><span style="color:#eceff4">,</span> <span style="color:#616e87;font-style:italic">// Length: 0</span>
</span></span><span style="display:flex;"><span>		<span style="color:#b48ead">0x04</span><span style="color:#eceff4">,</span>                   <span style="color:#616e87;font-style:italic">// Type: SETTINGS</span>
</span></span><span style="display:flex;"><span>		<span style="color:#b48ead">0x01</span><span style="color:#eceff4">,</span>                   <span style="color:#616e87;font-style:italic">// Flags: ACK (0x01)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#b48ead">0x00</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0x00</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0x00</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0x00</span><span style="color:#eceff4">,</span> <span style="color:#616e87;font-style:italic">// Stream ID: 0 (Connection level)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>ackHeader<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to send SETTINGS ACK: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Sent SETTINGS ACK.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<details >
    <summary>
        go/parser.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-2/go/parser.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;encoding/binary&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// FrameHeader represents the 9-byte fixed header of every HTTP/2 frame.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> FrameHeader <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Length   <span style="color:#81a1c1">uint32</span>
</span></span><span style="display:flex;"><span>	Type     <span style="color:#81a1c1">uint8</span>
</span></span><span style="display:flex;"><span>	Flags    <span style="color:#81a1c1">uint8</span>
</span></span><span style="display:flex;"><span>	StreamID <span style="color:#81a1c1">uint32</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Frame represents a complete HTTP/2 frame including its payload.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> Frame <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Header  FrameHeader
</span></span><span style="display:flex;"><span>	Payload <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ReadFrame reads a header and then the corresponding payload from the connection.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">ReadFrame</span><span style="color:#eceff4">(</span>r io<span style="color:#eceff4">.</span>Reader<span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>Frame<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 1. Read the 9-byte header</span>
</span></span><span style="display:flex;"><span>	headerBuf <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">9</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadFull</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">,</span> headerBuf<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> Frame<span style="color:#eceff4">{},</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;reading header: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 2. Parse the header fields using bit-shifting</span>
</span></span><span style="display:flex;"><span>	header <span style="color:#81a1c1">:=</span> FrameHeader<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Length<span style="color:#eceff4">:</span>   <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">])</span><span style="color:#81a1c1">&lt;&lt;</span><span style="color:#b48ead">16</span> <span style="color:#eceff4">|</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">])</span><span style="color:#81a1c1">&lt;&lt;</span><span style="color:#b48ead">8</span> <span style="color:#eceff4">|</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">2</span><span style="color:#eceff4">]),</span>
</span></span><span style="display:flex;"><span>		Type<span style="color:#eceff4">:</span>     headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">3</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>		Flags<span style="color:#eceff4">:</span>    headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">4</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>		StreamID<span style="color:#eceff4">:</span> binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uint32</span><span style="color:#eceff4">(</span>headerBuf<span style="color:#eceff4">[</span><span style="color:#b48ead">5</span><span style="color:#eceff4">:</span><span style="color:#b48ead">9</span><span style="color:#eceff4">])</span> <span style="color:#81a1c1">&amp;</span> <span style="color:#b48ead">0x7FFFFFFF</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// 3. Read the payload based on the Length field</span>
</span></span><span style="display:flex;"><span>	payload <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> header<span style="color:#eceff4">.</span>Length<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> header<span style="color:#eceff4">.</span>Length <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		_<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadFull</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">,</span> payload<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> Frame<span style="color:#eceff4">{},</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;reading payload: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> Frame<span style="color:#eceff4">{</span>Header<span style="color:#eceff4">:</span> header<span style="color:#eceff4">,</span> Payload<span style="color:#eceff4">:</span> payload<span style="color:#eceff4">},</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details></p>
]]></content:encoded></item><item><title>IRC Log: The Cloud Scale Incident</title><link>https://kmcd.dev/posts/irc-log-the-cloud-scale-incident/</link><pubDate>Mon, 23 Feb 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/irc-log-the-cloud-scale-incident/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/irc-log-the-cloud-scale-incident/cover.svg" /> &lt;/p>
                
                A vibe-coder attempts to provision a supercomputer to host a crypto-based to-do list app.
                </description><content:encoded><![CDATA[<p>An archived log from the #dev-help channel on the Freenode (RIP) afterlife network.</p>
<div class="chat-log" style="font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; background-color: #222; color: #eee; padding: 15px; border-radius: 5px; overflow-x: auto; line-height: 1.4; margin-bottom: 20px; font-size: 0.9rem; border: 1px solid #444;">


<div>
  <font color="#777777">[03:12]</font> 
  <font color="#4caf50"><b>*** Topic: reckless_dev is now known as broke_dev | Don&rsquo;t paste API keys</b></font>
</div>







<div>
  <font color="#777777">[03:14]</font> 
  
    <font color="#4caf50"><i>*** <font color="#ffca28"><b>vibe_coder_99</b></font> has joined</i></font>
  
</div>






<div>
  <font color="#777777">[03:15]</font> 
  <font color="#26c6da"><b>&lt;vibe_coder_99&gt;</b></font>
  <span>yo! anyone good with terraform? i&rsquo;m trying to launch my new crypto-based to-do list app and the tutorial is too slow.</span>
</div>






<div>
  <font color="#777777">[03:15]</font> 
  <font color="#42a5f5"><b>&lt;sysadmin_dave&gt;</b></font>
  <span>What tutorial? Also, crypto to-do list? Why?</span>
</div>






<div>
  <font color="#777777">[03:16]</font> 
  <font color="#26c6da"><b>&lt;vibe_coder_99&gt;</b></font>
  <span>it&rsquo;s strictly vibes based. i just need it to scale. like, infinite scale. i found this script on a forum that says it &ldquo;maximizes throughput&rdquo;.</span>
</div>






<div>
  <font color="#777777">[03:17]</font> 
  <font color="#ffa726"><b>&lt;cloud_guru&gt;</b></font>
  <span>Paste the plan. Don&rsquo;t just run random scripts.</span>
</div>






<div>
  <font color="#777777">[03:18]</font> 
  <font color="#26c6da"><b>&lt;vibe_coder_99&gt;</b></font>
  <span>nah its fine, i just want to know what &ldquo;p4d.24xlarge&rdquo; means. sounds powerful. i changed the count to 50 just to be safe for launch day.</span>
</div>






<div>
  <font color="#777777">[03:18]</font> 
  <font color="#42a5f5"><b>&lt;sysadmin_dave&gt;</b></font>
  <span>STOP.</span>
</div>






<div>
  <font color="#777777">[03:18]</font> 
  <font color="#42a5f5"><b>&lt;sysadmin_dave&gt;</b></font>
  <span>DO NOT RUN THAT.</span>
</div>






<div>
  <font color="#777777">[03:19]</font> 
  <font color="#ffa726"><b>&lt;cloud_guru&gt;</b></font>
  <span>That instance is $32 an hour. PER INSTANCE.</span>
</div>






<div>
  <font color="#777777">[03:19]</font> 
  <font color="#26c6da"><b>&lt;vibe_coder_99&gt;</b></font>
  <span>wait really? lol whatever i have $100 in credits. running <code>terraform apply --auto-approve</code> now.</span>
</div>







<div>
  <font color="#777777">[03:19]</font> 
  
    <font color="#e040fb"><i>* <font color="#ffca28"><b>sysadmin_dave</b></font> screams internally</i></font>
  
</div>






<div>
  <font color="#777777">[03:22]</font> 
  <font color="#26c6da"><b>&lt;vibe_coder_99&gt;</b></font>
  <span>man aws is slow today. it&rsquo;s been provisioning for like 2 minutes.</span>
</div>






<div>
  <font color="#777777">[03:22]</font> 
  <font color="#ffa726"><b>&lt;cloud_guru&gt;</b></font>
  <span>You are requesting 50 top-tier GPU instances. You are trying to provision a supercomputer to host a to-do list.</span>
</div>






<div>
  <font color="#777777">[03:23]</font> 
  <font color="#26c6da"><b>&lt;vibe_coder_99&gt;</b></font>
  <span>gotta go fast right? 🚀</span>
</div>






<div>
  <font color="#777777">[03:24]</font> 
  <font color="#26c6da"><b>&lt;vibe_coder_99&gt;</b></font>
  <span>uh guys</span>
</div>






<div>
  <font color="#777777">[03:25]</font> 
  <font color="#26c6da"><b>&lt;vibe_coder_99&gt;</b></font>
  <span>my bank called. fraud alert. something about &ldquo;unusual spending pattern&rdquo;.</span>
</div>






<div>
  <font color="#777777">[03:25]</font> 
  <font color="#42a5f5"><b>&lt;sysadmin_dave&gt;</b></font>
  <span>You just burned your credits in 4 minutes. You are now spending ~$1600/hour.</span>
</div>






<div>
  <font color="#777777">[03:26]</font> 
  <font color="#26c6da"><b>&lt;vibe_coder_99&gt;</b></font>
  <span>how do i undo??? ctrl-z doesn&rsquo;t work in terminal!!</span>
</div>






<div>
  <font color="#777777">[03:26]</font> 
  <font color="#42a5f5"><b>&lt;sysadmin_dave&gt;</b></font>
  <span><code>terraform destroy</code>. PRAY that the API isn&rsquo;t rate limiting you.</span>
</div>






<div>
  <font color="#777777">[03:27]</font> 
  <font color="#26c6da"><b>&lt;vibe_coder_99&gt;</b></font>
  <span>it says &ldquo;state lock&rdquo;. i think i closed the window too fast.</span>
</div>







<div>
  <font color="#777777">[03:28]</font> 
  
    <font color="#ff5722"><i>*** <font color="#ffca28"><b>vibe_coder_99</b></font> has left</i></font>
  
</div>


<div>
  <font color="#777777">[03:28]</font> 
  <font color="#4caf50"><b>*** vibe_coder_99 has quit (Connection reset by peer: fleeing the country)</b></font>
</div>


</div>

]]></content:encoded></item><item><title>Visualizing the Internet (2026)</title><link>https://kmcd.dev/posts/internet-map-2026/</link><pubDate>Wed, 18 Feb 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/internet-map-2026/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/internet-map-2026/cover.svg" /> &lt;/p>
                
                Mapping global Internet infrastructure and routing dominance over time
                </description><content:encoded><![CDATA[<p>For the past few years, I’ve been trying to make the physical reality of the Internet visible with my <a href="https://map.kmcd.dev" rel="external">Internet Infrastructure Map</a>. This map shows the network of undersea fiber-optic cables along with peering bandwidth, grouped by city. I update the map annually, but I don’t want to just pull the latest data and call it a day. In this post I discuss how the map evolved this year and what I did to make it happen, but you can skip to the good part by viewing it here: <strong><a href="https://map.kmcd.dev" rel="external">map.kmcd.dev</a></strong>.</p>
<p>For the 2026 edition, I wanted to better answer the question: where does the Internet <em>actually</em> live? By layering on BGP routing tables alongside physical infrastructure data, I’m now closer to answering that question.</p>
<p>The result is a concept I call &ldquo;Logical Dominance.&rdquo; Each city’s dominance is calculated by summing total address space of IPv4 subnets that are &ldquo;homed&rdquo; in that city. How can I tell where IP addresses are homed? This required analyzing global routing tables to trace IP ownership back to specific geographies. Read on to find out how I accomplished this!</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">











    
        
    



    
        
    




    




    


<figure>
    <div style="display: flex; gap: 10px;">
        <div style="flex: 1;">
            <p><strong>Before:</strong></p>
            <img src="https://kmcd.dev/posts/internet-map-2026/map_dark.svg" alt="Before" style="max-width: 100%; height: auto;">
        </div>
        <div style="flex: 1;">
            <p><strong>After:</strong></p>
            <img src="https://kmcd.dev/posts/internet-map-2026/map_light.svg" alt="After" style="max-width: 100%; height: auto;">
        </div>
    </div>
    
    <figcaption>Internet Infrastructure Map (2026) @ <strong><a href="https://map.kmcd.dev" rel="external">map.kmcd.dev</a></strong></figcaption>
    
</figure>


    </span>

    
</div>

<h3 id="how-the-internet-routes-traffic">How the Internet Routes Traffic</h3>
<p>Previous versions of the map focused on physical infrastructure: cables and exchange points. The physical path is only half the story. To understand how data moves, we have to look at <strong>BGP (Border Gateway Protocol)</strong>.</p>
<p>BGP is the protocol that distinct networks, known as <strong>Autonomous Systems (AS)</strong>, use to announce which IP addresses they own and how to reach them. If the cables are the hardware, BGP is the software that ties the Internet together. <a href="https://www.cloudflare.com/learning/security/glossary/what-is-bgp/" rel="external">Cloudflare has an excellent primer</a>.</p>
<p>When you load a webpage, your request doesn&rsquo;t just &ldquo;know&rdquo; the path. Your ISP’s routers consult the global BGP routing table to decide the best next hop. Visualized, it looks a little bit like this:</p>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/22b7440b1ec0b818367632c4a9de8cfa1cccba579e06150a580a74020182f52a.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<p>In this state, the route from <code>Router</code> -&gt; <code>Netstream (AS8283)</code> -&gt; <code>Google (AS15169)</code> was chosen, at least for now. The underlying routes of the global Internet change thousands of times per second, constantly reshaping the topology.</p>
<h4 id="sources-of-bgp-data">Sources of BGP Data</h4>
<p>To visualize this layer, we need access to routing tables. I explored three ways to get this data, each with its own trade-offs between real-time visibility and historical context.</p>
<h4 id="query-a-looking-glass">Query a Looking Glass</h4>
<p>We can connect to public routers via projects like <a href="http://www.routeviews.org/" rel="external">University of Oregon Route Views</a>. These allow you to telnet in and run standard CLI commands like <code>show ip bgp</code> to see exactly what a backbone router sees.</p>
<details >
    <summary>
        BGP routes for 8.8.8.8 (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/internet-map-2026/routeviews-log.txt" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ telnet route-views.routeviews.org <span style="color:#b48ead">23</span>
</span></span><span style="display:flex;"><span>**********************************************************************
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    RouteViews BGP Route Viewer
</span></span><span style="display:flex;"><span>                    route-views.routeviews.org
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> RouteViews data is archived on https://archive.routeviews.org
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> This hardware is part of a grant by the NSF.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> Please contact help@routeviews.org <span style="color:#81a1c1;font-weight:bold">if</span> you have questions, or
</span></span><span style="display:flex;"><span> <span style="color:#81a1c1;font-weight:bold">if</span> you wish to contribute your view.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> This router has views of full routing tables from several ASes.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> The current list of all RouteViews peers is at
</span></span><span style="display:flex;"><span>   https://www.routeviews.org/peers/peering-status.html
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> NOTE: If you are using macOS and seeing the error message
</span></span><span style="display:flex;"><span> <span style="color:#a3be8c">&#34;no default Kerberos realm&#34;</span> when logging in, you may want to
</span></span><span style="display:flex;"><span> add <span style="color:#a3be8c">&#34;default unset autologin&#34;</span> to your ~/.telnetrc
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> To login, use the username <span style="color:#a3be8c">&#34;rviews&#34;</span>.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>**********************************************************************
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>User Access Verification
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Username: rviews
</span></span><span style="display:flex;"><span>route-views&gt;show ip bgp 8.8.8.8
</span></span><span style="display:flex;"><span>BGP routing table entry <span style="color:#81a1c1;font-weight:bold">for</span> 8.8.8.0/24, version <span style="color:#b48ead">941738530</span>
</span></span><span style="display:flex;"><span>Paths: <span style="color:#81a1c1">(</span><span style="color:#b48ead">16</span> available, best <span style="color:#616e87;font-style:italic">#15, table default)</span>
</span></span><span style="display:flex;"><span>  Not advertised to any peer
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">4826</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    114.31.199.16 from 114.31.199.16 <span style="color:#81a1c1">(</span>114.31.199.16<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      Community: 4826:5203 4826:6510 4826:52032
</span></span><span style="display:flex;"><span>      path 7F168F059710 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">57866</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    37.139.139.17 from 37.139.139.17 <span style="color:#81a1c1">(</span>37.139.139.17<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, metric 0, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      Community: 57866:200 65102:56393 65103:1 65104:31
</span></span><span style="display:flex;"><span>      unknown transitive attribute: flag 0xE0 <span style="color:#81a1c1">type</span> 0x20 length 0x30
</span></span><span style="display:flex;"><span>        value <span style="color:#b48ead">0000</span> E20A <span style="color:#b48ead">0000</span> <span style="color:#b48ead">0065</span> <span style="color:#b48ead">0000</span> 00C8 <span style="color:#b48ead">0000</span> E20A
</span></span><span style="display:flex;"><span>              <span style="color:#b48ead">0000</span> <span style="color:#b48ead">0066</span> <span style="color:#b48ead">0000</span> DC49 <span style="color:#b48ead">0000</span> E20A <span style="color:#b48ead">0000</span> <span style="color:#b48ead">0067</span>
</span></span><span style="display:flex;"><span>              <span style="color:#b48ead">0000</span> <span style="color:#b48ead">0001</span> <span style="color:#b48ead">0000</span> E20A <span style="color:#b48ead">0000</span> <span style="color:#b48ead">0068</span> <span style="color:#b48ead">0000</span> 001F
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      path 7F15A63304E8 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">6939</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    64.71.137.241 from 64.71.137.241 <span style="color:#81a1c1">(</span>216.218.253.53<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      path 7F1555102CB8 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">20130</span> <span style="color:#b48ead">6939</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    140.192.8.16 from 140.192.8.16 <span style="color:#81a1c1">(</span>140.192.8.16<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      path 7F1588A8BD60 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">3333</span> <span style="color:#b48ead">1257</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    193.0.0.56 from 193.0.0.56 <span style="color:#81a1c1">(</span>193.0.0.56<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      Community: 1257:50 1257:51 1257:3528
</span></span><span style="display:flex;"><span>      path 7F16B16DD098 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">7018</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    12.0.1.63 from 12.0.1.63 <span style="color:#81a1c1">(</span>12.0.1.63<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      Community: 7018:2500 7018:37232
</span></span><span style="display:flex;"><span>      path 7F1626828FD8 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">20912</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    77.39.192.30 from 77.39.192.30 <span style="color:#81a1c1">(</span>77.39.192.1<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      Community: 20912:65002 20912:65022
</span></span><span style="display:flex;"><span>      path 7F15D19801C0 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">3356</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    4.68.4.46 from 4.68.4.46 <span style="color:#81a1c1">(</span>4.69.184.201<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, metric 0, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      Community: 3356:3 3356:86 3356:576 3356:666 3356:901 3356:2012
</span></span><span style="display:flex;"><span>      path 7F1679EB7640 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">3549</span> <span style="color:#b48ead">3356</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    208.51.134.254 from 208.51.134.254 <span style="color:#81a1c1">(</span>67.16.168.191<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, metric 0, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      Community: 3356:3 3356:22 3356:86 3356:575 3356:666 3356:901 3356:2011 3549:2581 3549:30840
</span></span><span style="display:flex;"><span>      path 7F16C7D32728 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">101</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    209.124.176.223 from 209.124.176.223 <span style="color:#81a1c1">(</span>209.124.176.224<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      Community: 101:20400 101:22200 101:24100
</span></span><span style="display:flex;"><span>      path 7F1648388978 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">1351</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    132.198.255.253 from 132.198.255.253 <span style="color:#81a1c1">(</span>132.198.255.253<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      path 7F15C90D61B8 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">3257</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    89.149.178.10 from 89.149.178.10 <span style="color:#81a1c1">(</span>213.200.83.26<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, metric 10, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      Community: 3257:8052 3257:30306 3257:50001 3257:54900 3257:54901
</span></span><span style="display:flex;"><span>      path 7F154665E650 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">2497</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    202.232.0.2 from 202.232.0.2 <span style="color:#81a1c1">(</span>58.138.96.254<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      path 7F1660B0A1C8 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">2</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">3303</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    217.192.89.50 from 217.192.89.50 <span style="color:#81a1c1">(</span>138.187.128.158<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      Community: 3303:1004 3303:1007 3303:3067
</span></span><span style="display:flex;"><span>      path 7F16E10C0508 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">8283</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    94.142.247.3 from 94.142.247.3 <span style="color:#81a1c1">(</span>94.142.247.3<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, localpref 100, valid, external, best
</span></span><span style="display:flex;"><span>      Community: 8283:1 8283:101 8283:102
</span></span><span style="display:flex;"><span>      unknown transitive attribute: flag 0xE0 <span style="color:#81a1c1">type</span> 0x20 length 0x30
</span></span><span style="display:flex;"><span>        value <span style="color:#b48ead">0000</span> 205B <span style="color:#b48ead">0000</span> <span style="color:#b48ead">0000</span> <span style="color:#b48ead">0000</span> <span style="color:#b48ead">0001</span> <span style="color:#b48ead">0000</span> 205B
</span></span><span style="display:flex;"><span>              <span style="color:#b48ead">0000</span> <span style="color:#b48ead">0005</span> <span style="color:#b48ead">0000</span> <span style="color:#b48ead">0001</span> <span style="color:#b48ead">0000</span> 205B <span style="color:#b48ead">0000</span> <span style="color:#b48ead">0005</span>
</span></span><span style="display:flex;"><span>              <span style="color:#b48ead">0000</span> <span style="color:#b48ead">0002</span> <span style="color:#b48ead">0000</span> 205B <span style="color:#b48ead">0000</span> <span style="color:#b48ead">0008</span> <span style="color:#b48ead">0000</span> 001A
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      path 7F16DC0E5B28 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: 0x0
</span></span><span style="display:flex;"><span>  Refresh Epoch <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b48ead">49788</span> <span style="color:#b48ead">12552</span> <span style="color:#b48ead">15169</span>
</span></span><span style="display:flex;"><span>    91.218.184.60 from 91.218.184.60 <span style="color:#81a1c1">(</span>91.218.184.60<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>      Origin IGP, localpref 100, valid, external
</span></span><span style="display:flex;"><span>      Community: 12552:10000 12552:14000 12552:14100 12552:14101 12552:24000
</span></span><span style="display:flex;"><span>      Extended Community: 0x43:100:0
</span></span><span style="display:flex;"><span>      path 7F15CAD0C378 RPKI State valid
</span></span><span style="display:flex;"><span>      rx pathid: 0, tx pathid: <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span>route-views&gt;⏎
</span></span></code></pre></div>
</details>
<p>These paths often carry metadata called <strong>BGP Communities</strong>. These are optional tags that networks use to signal things like geographic origin or peering policy. While perfect for debugging today’s Internet, this approach lacks historical context; you can’t telnet into 2012 to check a routing table from 14 years ago.</p>
<h4 id="subscribe-to-a-stream">Subscribe to a Stream</h4>
<p>For real-time views, services like <strong>RIPE RIS Live</strong> aggregate BGP data from global collectors and stream it over a public WebSocket. You can watch the Internet &ldquo;breathe&rdquo; as routes are announced and withdrawn thousands of times per second. This is fascinating for a live dashboard, but useless for backfilling history.</p>
<p>Here&rsquo;s an example script consuming this stream:
<details >
    <summary>
        go/stream_bgp/main.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/internet-map-2026/go/stream_bgp/main.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>package main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>import <span style="color:#81a1c1">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;os/signal&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;github.com/gorilla/websocket&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>// RIPE RIS Live WebSocket URL
</span></span><span style="display:flex;"><span>const risLiveURL <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;wss://ris-live.ripe.net/v1/ws/?client=kmcd-internet-map&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>// Message defines the structure of the JSON messages we receive
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">type</span> Message struct <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>	Type string                 <span style="color:#a3be8c">`</span>json:<span style="color:#a3be8c">&#34;type&#34;</span><span style="color:#a3be8c">`</span>
</span></span><span style="display:flex;"><span>	Data map<span style="color:#81a1c1">[</span>string<span style="color:#81a1c1">]</span>interface<span style="color:#81a1c1">{}</span> <span style="color:#a3be8c">`</span>json:<span style="color:#a3be8c">&#34;data&#34;</span><span style="color:#a3be8c">`</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>func main<span style="color:#81a1c1">()</span> <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>	// Handle Ctrl+C gracefully
</span></span><span style="display:flex;"><span>	interrupt :<span style="color:#81a1c1">=</span> make<span style="color:#81a1c1">(</span>chan os.Signal, 1<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>	signal.Notify<span style="color:#81a1c1">(</span>interrupt, os.Interrupt<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt.Printf<span style="color:#81a1c1">(</span><span style="color:#a3be8c">&#34;Connecting to %s...\n&#34;</span>, risLiveURL<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	c, _, err :<span style="color:#81a1c1">=</span> websocket.DefaultDialer.Dial<span style="color:#81a1c1">(</span>risLiveURL, nil<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err !<span style="color:#81a1c1">=</span> nil <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>		log.Fatal<span style="color:#81a1c1">(</span><span style="color:#a3be8c">&#34;dial:&#34;</span>, err<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>	defer c.Close<span style="color:#81a1c1">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	// Subscribe to the firehose <span style="color:#81a1c1">(</span>all messages<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>	// You can filter this! e.g., <span style="color:#81a1c1">{</span><span style="color:#a3be8c">&#34;host&#34;</span>: <span style="color:#a3be8c">&#34;rrc21&#34;</span><span style="color:#81a1c1">}</span> <span style="color:#81a1c1;font-weight:bold">for</span> a specific collector
</span></span><span style="display:flex;"><span>	subscribeMsg :<span style="color:#81a1c1">=</span> map<span style="color:#81a1c1">[</span>string<span style="color:#81a1c1">]</span>interface<span style="color:#81a1c1">{}{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a3be8c">&#34;type&#34;</span>: <span style="color:#a3be8c">&#34;ris_subscribe&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a3be8c">&#34;data&#34;</span>: map<span style="color:#81a1c1">[</span>string<span style="color:#81a1c1">]</span>interface<span style="color:#81a1c1">{}{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#a3be8c">&#34;moreSpecific&#34;</span>: true,
</span></span><span style="display:flex;"><span>			<span style="color:#a3be8c">&#34;type&#34;</span>:         <span style="color:#a3be8c">&#34;UPDATE&#34;</span>, // Only show route updates
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1">}</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err :<span style="color:#81a1c1">=</span> c.WriteJSON<span style="color:#81a1c1">(</span>subscribeMsg<span style="color:#81a1c1">)</span><span style="color:#eceff4">;</span> err !<span style="color:#81a1c1">=</span> nil <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>		log.Fatal<span style="color:#81a1c1">(</span><span style="color:#a3be8c">&#34;subscribe:&#34;</span>, err<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt.Println<span style="color:#81a1c1">(</span><span style="color:#a3be8c">&#34;Connected! Streaming global BGP updates...&#34;</span><span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>	fmt.Println<span style="color:#81a1c1">(</span><span style="color:#a3be8c">&#34;------------------------------------------------&#34;</span><span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">done</span> :<span style="color:#81a1c1">=</span> make<span style="color:#81a1c1">(</span>chan struct<span style="color:#81a1c1">{})</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	go func<span style="color:#81a1c1">()</span> <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>		defer close<span style="color:#81a1c1">(</span><span style="color:#81a1c1;font-weight:bold">done</span><span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>			var msg Message
</span></span><span style="display:flex;"><span>			err :<span style="color:#81a1c1">=</span> c.ReadJSON<span style="color:#81a1c1">(</span><span style="color:#eceff4">&amp;</span>msg<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> err !<span style="color:#81a1c1">=</span> nil <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>				log.Println<span style="color:#81a1c1">(</span><span style="color:#a3be8c">&#34;read:&#34;</span>, err<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>			// We only care about BGP UPDATE messages 
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> msg.Type <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;ris_message&#34;</span> <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>				path :<span style="color:#81a1c1">=</span> msg.Data<span style="color:#81a1c1">[</span><span style="color:#a3be8c">&#34;path&#34;</span><span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>				prefix :<span style="color:#81a1c1">=</span> msg.Data<span style="color:#81a1c1">[</span><span style="color:#a3be8c">&#34;announcements&#34;</span><span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>				
</span></span><span style="display:flex;"><span>				// Handle withdrawals <span style="color:#81a1c1">(</span>routes being removed<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">if</span> prefix <span style="color:#81a1c1">==</span> nil <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>					prefix <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;WITHDRAWAL&#34;</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>				// Print the timestamp, the route prefix, and the AS path
</span></span><span style="display:flex;"><span>				fmt.Printf<span style="color:#81a1c1">(</span><span style="color:#a3be8c">&#34;[%s] Prefix: %v | Path: %v\n&#34;</span>, 
</span></span><span style="display:flex;"><span>					time.Now<span style="color:#81a1c1">()</span>.Format<span style="color:#81a1c1">(</span><span style="color:#a3be8c">&#34;15:04:05&#34;</span><span style="color:#81a1c1">)</span>, 
</span></span><span style="display:flex;"><span>					prefix, 
</span></span><span style="display:flex;"><span>					path,
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1">}()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	// Wait <span style="color:#81a1c1;font-weight:bold">for</span> interrupt
</span></span><span style="display:flex;"><span>	&lt;-interrupt
</span></span><span style="display:flex;"><span>	fmt.Println<span style="color:#81a1c1">(</span><span style="color:#a3be8c">&#34;\nDisconnecting...&#34;</span><span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>	err <span style="color:#81a1c1">=</span> c.WriteMessage<span style="color:#81a1c1">(</span>websocket.CloseMessage, websocket.FormatCloseMessage<span style="color:#81a1c1">(</span>websocket.CloseNormalClosure, <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#81a1c1">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err !<span style="color:#81a1c1">=</span> nil <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>		log.Println<span style="color:#81a1c1">(</span><span style="color:#a3be8c">&#34;write close:&#34;</span>, err<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">select</span> <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">case</span> &lt;-done:
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">case</span> &lt;-time.After<span style="color:#81a1c1">(</span>time.Second<span style="color:#81a1c1">)</span>:
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div>
</details></p>
<p>The output looks like this:
<details >
    <summary>
        Websocket Stream Output (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/internet-map-2026/go/stream_bgp/output.txt" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>Connecting to wss://ris-live.ripe.net/v1/ws/?client<span style="color:#81a1c1">=</span>kmcd-internet-map...
</span></span><span style="display:flex;"><span>Connected! Streaming global BGP updates...
</span></span><span style="display:flex;"><span>------------------------------------------------
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">[</span>22:52:35<span style="color:#81a1c1">]</span> Prefix: <span style="color:#81a1c1">[</span>map<span style="color:#81a1c1">[</span>next_hop:2001:7f8:24::b1 prefixes:<span style="color:#81a1c1">[</span>2804:70c0::/32<span style="color:#81a1c1">]]]</span> <span style="color:#eceff4">|</span> Path: <span style="color:#81a1c1">[</span><span style="color:#b48ead">58057</span> <span style="color:#b48ead">6939</span> <span style="color:#b48ead">22381</span> <span style="color:#b48ead">1031</span> <span style="color:#b48ead">263444</span> <span style="color:#b48ead">22381</span> 270746<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">[</span>22:52:35<span style="color:#81a1c1">]</span> Prefix: <span style="color:#81a1c1">[</span>map<span style="color:#81a1c1">[</span>next_hop:2001:7f8:24::aa,fe80::8a7e:25ff:fed3:420b prefixes:<span style="color:#81a1c1">[</span>2a13:9404::/32<span style="color:#81a1c1">]]]</span> <span style="color:#eceff4">|</span> Path: <span style="color:#81a1c1">[</span><span style="color:#b48ead">6939</span> <span style="color:#b48ead">215120</span> 34689<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">[</span>22:52:35<span style="color:#81a1c1">]</span> Prefix: <span style="color:#81a1c1">[</span>map<span style="color:#81a1c1">[</span>next_hop:2001:7f8:24::aa,fe80::470:71ff:fec5:b6ad prefixes:<span style="color:#81a1c1">[</span>2a14:7c0:1740::/48 2a10:ccc7:b110::/44<span style="color:#81a1c1">]]]</span> <span style="color:#eceff4">|</span> Path: <span style="color:#81a1c1">[</span><span style="color:#b48ead">196621</span> <span style="color:#b48ead">6939</span> <span style="color:#b48ead">215120</span> <span style="color:#b48ead">214497</span> <span style="color:#b48ead">6204</span> <span style="color:#b48ead">215120</span> 214497<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">[</span>22:52:35<span style="color:#81a1c1">]</span> Prefix: <span style="color:#81a1c1">[</span>map<span style="color:#81a1c1">[</span>next_hop:2001:7f8:24::aa,fe80::224:38ff:fea4:a907 prefixes:<span style="color:#81a1c1">[</span>2a14:7c0:1740::/48 2a10:ccc7:b110::/44<span style="color:#81a1c1">]]]</span> <span style="color:#eceff4">|</span> Path: <span style="color:#81a1c1">[</span><span style="color:#b48ead">29691</span> <span style="color:#b48ead">6939</span> <span style="color:#b48ead">215120</span> <span style="color:#b48ead">214497</span> <span style="color:#b48ead">6204</span> <span style="color:#b48ead">215120</span> 214497<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">[</span>22:52:35<span style="color:#81a1c1">]</span> Prefix: <span style="color:#81a1c1">[</span>map<span style="color:#81a1c1">[</span>next_hop:91.206.52.177 prefixes:<span style="color:#81a1c1">[</span>2a10:ccc7:b110::/44 2a14:7c0:1740::/48<span style="color:#81a1c1">]]]</span> <span style="color:#eceff4">|</span> Path: <span style="color:#81a1c1">[</span><span style="color:#b48ead">58057</span> <span style="color:#b48ead">6939</span> <span style="color:#b48ead">215120</span> <span style="color:#b48ead">214497</span> <span style="color:#b48ead">6204</span> <span style="color:#b48ead">215120</span> 214497<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">[</span>22:52:35<span style="color:#81a1c1">]</span> Prefix: <span style="color:#81a1c1">[</span>map<span style="color:#81a1c1">[</span>next_hop:2001:7f8:24::b1 prefixes:<span style="color:#81a1c1">[</span>2803:5d10::/32<span style="color:#81a1c1">]]]</span> <span style="color:#eceff4">|</span> Path: <span style="color:#81a1c1">[</span><span style="color:#b48ead">58057</span> <span style="color:#b48ead">6939</span> <span style="color:#b48ead">3356</span> <span style="color:#b48ead">28343</span> 272053<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">[</span>22:52:35<span style="color:#81a1c1">]</span> Prefix: <span style="color:#81a1c1">[</span>map<span style="color:#81a1c1">[</span>next_hop:2001:7f8:24::aa,fe80::8a7e:25ff:fed3:420b prefixes:<span style="color:#81a1c1">[</span>2a14:7c0:1740::/48 2a10:ccc7:b110::/44<span style="color:#81a1c1">]]]</span> <span style="color:#eceff4">|</span> Path: <span style="color:#81a1c1">[</span><span style="color:#b48ead">6939</span> <span style="color:#b48ead">215120</span> 214497<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">[</span>22:52:35<span style="color:#81a1c1">]</span> Prefix: <span style="color:#81a1c1">[</span>map<span style="color:#81a1c1">[</span>next_hop:2001:7f8:24::aa,fe80::470:71ff:fec5:b6ad prefixes:<span style="color:#81a1c1">[</span>2a13:9404::/32<span style="color:#81a1c1">]]]</span> <span style="color:#eceff4">|</span> Path: <span style="color:#81a1c1">[</span><span style="color:#b48ead">196621</span> <span style="color:#b48ead">6939</span> <span style="color:#b48ead">215120</span> 34689<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>2026/02/09 22:52:35 read: websocket: close <span style="color:#b48ead">1000</span> <span style="color:#81a1c1">(</span>normal<span style="color:#81a1c1">)</span>
</span></span></code></pre></div>
</details></p>
<h4 id="download-historical-snapshots">Download Historical Snapshots</h4>
<p>To build the historical model, I processed raw <strong>RIB (Routing Information Base)</strong> files. These are snapshots of the entire routing table as seen by a backbone router at a specific moment in time. Because BGP is a &ldquo;chatter&rdquo; protocol that only announces changes, these full table dumps are essential for reconstructing the state of the Internet at any point in the past.</p>
<p>I specifically fetched snapshots from February 1st at 12:00 UTC for every year in my timeline. To ensure a comprehensive view, I aggregated data from multiple global collectors maintained by the <a href="http://archive.routeviews.org/" rel="external">University of Oregon Route Views</a> project.</p>
<p>Other excellent resources for this kind of data include:</p>
<ul>
<li><strong><a href="https://www.ripe.net/analyse/internet-measurements/routing-information-service-ris/ris-raw-data/" rel="external">RIPE RIS (Routing Information Service)</a>:</strong> Provides high-fidelity snapshots from a dense network of collectors, primarily in Europe.</li>
<li><strong><a href="https://bgpstream.caida.org/" rel="external">CAIDA BGP Stream</a>:</strong> A framework for analyzing both real-time and historical data from various sources.</li>
</ul>
<hr>
<h3 id="how-bgp-shapes-the-global-internet-map">How BGP Shapes the Global Internet Map</h3>
<p>For this edition, I processed over 15 years of BGP snapshots and PeeringDB archives to build the Logical Dominance model. Reconstructing this history was easily the hardest part of the project. I quickly realized that reliable archival data for physical peering effectively vanishes before 2010, which set a hard limit on how far back I could take the timeline.</p>
<h4 id="defining-logical-dominance">Defining Logical Dominance</h4>
<p>Logical Dominance is calculated by summing the number of unique IPv4 addresses originated by an ASN and attributed to a given city. Overlapping prefixes are deduplicated using longest-prefix normalization so that no address space is counted twice.</p>
<h4 id="the-scaling-problem-why-not-ipv6">The Scaling Problem: Why not IPv6?</h4>
<p>You might notice this model focuses entirely on IPv4. While IPv6 is the future of the protocol, its sheer scale currently breaks the &ldquo;Logical Dominance&rdquo; math. I measure dominance by counting unique IP addresses; if I treated IPv4 and IPv6 as equals, the numbers wouldn&rsquo;t just be skewed; they’d be nonsensical.</p>
<p>Consider the math: The smallest standard IPv6 assignment is a <code>/64</code>. That single subnet contains <code>18,446,744,073,709,551,616</code> addresses. You could fit the entire global IPv4 routing table (<code>4,294,967,296</code> addresses) inside that one subnet <strong>4.3 billion times over</strong>.</p>
<p>If I treated every IP equally, a single residential IPv6 connection would statistically obliterate a city hosting the entire legacy IPv4 Internet. Until I develop a weighted model for IPv6, perhaps based on prefix density rather than raw address count, IPv4 remains the only way to compare global &ldquo;weight&rdquo; on a 1:1 scale.</p>
<h4 id="finding-the-truth-in-the-noise">Finding the Truth in the Noise</h4>
<p>Mapping a BGP prefix to a specific city is more difficult than you may think. A subnet might be registered to a corporate HQ but serve users thousands of miles away. To solve this, I built a prioritized &ldquo;waterfall&rdquo; of attribution logic. I check sources in order of reliability, stopping as soon as I find a match:</p>
<ol>
<li><strong><a href="https://datatracker.ietf.org/doc/html/rfc8805" rel="external">Geofeeds (RFC 8805)</a>:</strong> These are machine-readable CSVs where network operators explicitly self-report where their subnets are used.</li>
<li><strong>Cloud Provider Ranges:</strong> I ingest live IP lists from <a href="https://docs.aws.amazon.com/general/latest/gr/aws-ip-ranges.html" rel="external">AWS</a>, <a href="https://docs.cloud.google.com/compute/docs/faq#find_ip_range" rel="external">Google Cloud</a>, and others, mapping logical regions (like <code>eu-west-1</code>) to their physical locations (Dublin).</li>
<li><strong>Network Hints (Communities &amp; Next-Hops):</strong> At this point, I look to the routing table itself for hints. If a prefix is only announced at the London Internet Exchange, or tagged with a &ldquo;London&rdquo; <a href="https://networklessons.com/bgp/bgp-communities-explained" rel="external">BGP Community</a>, I attribute it there.</li>
<li><strong>Historical WHOIS:</strong> My final fallback for specific location data is the <a href="https://www.apnic.net/about-apnic/whois_search/" rel="external">APNIC</a>/RIPE databases.</li>
<li><strong>Footprint Heuristic:</strong> For anything remaining, I assign the IP weight to every city where that network maintains physical peering capacity as listed in <a href="https://www.peeringdb.com/" rel="external">PeeringDB</a>.</li>
</ol>
<p>This approach ensures that accurate, granular data (like a specific cloud region) always overrides broad, administrative data (like a generic WHOIS entry).</p>
<p>Building this pipeline presented unique engineering hurdles; here are the most significant ones:</p>
<h4 id="the-local-cache">The Local Cache</h4>
<p>Downloading 15 years of archives is slow. I threw together a quick file-based cache to avoid hitting the network repeatedly. It was the simplest code I wrote but easily the most valuable, turning 30-minute download waits into near-instant local reads.</p>
<h4 id="ram-remains-stubbornly-finite">RAM remains stubbornly finite</h4>
<p>Loading millions of IP prefixes, WHOIS records, PeeringDB entries, and their associated metadata into a standard in-memory map consumes gigabytes of RAM instantly. Frustratingly, my laptop only has so much. To avoid out-of-memory errors I built a custom <strong>on-disk <a href="https://en.wikipedia.org/wiki/Trie" rel="external">trie data structure</a></strong> using <a href="https://github.com/dgraph-io/badger" rel="external"><strong>BadgerDB v4</strong></a>, which is a Go KV store built on an LSM tree, which makes IP prefix lookups very efficient. I might show it off in a later blog post after I clean it up a little bit. By using IP prefixes as keys in a sorted KV store, I can perform efficient longest-prefix matching directly against the disk.</p>
<h4 id="cleaning-up-the-spaghetti">Cleaning Up the Spaghetti</h4>
<p>While investigating all of these different data sources, I ended up writing several programs that generated output of different shapes that would be used by other programs. It all made sense to me at the time but it spiraled out of control into a confusing mess. Now, I have one script for generating this city data. I was only able to do this because of the improvements mentioned above: caching and using on-disk data structures. Now, the script has clear stages of:</p>
<ul>
<li><strong>Fetch:</strong> Downloads and caches raw data (WHOIS, BGP, PeeringDB).</li>
<li><strong>Index:</strong> Builds searchable on-disk tries and resolves authoritative network names from RIRs.</li>
<li><strong>Process:</strong> Scans BGP routes and attributes each prefix using the various data sources mentioned above.</li>
<li><strong>Output:</strong> Produces clean, normalized city results without duplicate entries (e.g., merging &ldquo;Seoul&rdquo; and &ldquo;SEOUL&rdquo;).</li>
</ul>
<h3 id="what-changed-when-ip-dominance-was-added">What Changed When IP Dominance Was Added</h3>
<p>When I layered IP dominance onto the physical map, many additional cities became visible.</p>











    
        
    



    
        
    




    




    


<figure>
    <div style="display: flex; gap: 10px;">
        <div style="flex: 1;">
            <p><strong>Before:</strong></p>
            <img src="https://kmcd.dev/posts/internet-map-2026/map_2026_before.svg" alt="Before" style="max-width: 100%; height: auto;">
        </div>
        <div style="flex: 1;">
            <p><strong>After:</strong></p>
            <img src="https://kmcd.dev/posts/internet-map-2026/map_2026_after.svg" alt="After" style="max-width: 100%; height: auto;">
        </div>
    </div>
    
    <figcaption>World (<a href="https://map.kmcd.dev/?lat=29.8864&amp;lng=6.8171&amp;z=3.50&amp;year=2026&amp;cables=0&amp;peering=1&amp;ips=0" rel="external">before</a> and <a href="https://map.kmcd.dev/?lat=29.8864&amp;lng=6.8171&amp;z=3.50&amp;year=2026&amp;cables=0&amp;peering=1&amp;ips=1" rel="external">after</a> adding BGP data).</figcaption>
    
</figure>

<p>In earlier versions, visibility depended heavily on registered Internet Exchange Points. That highlighted the traditional coastal hubs and major peering metros. But once routing table data was incorporated, the map revealed cities without major IXPs. These are places with substantial address space and large originating networks, even if they do not host a major public exchange. This is most noticeable in India, Japan, China, Indonesia, and in secondary metros beyond traditional hubs in the EU and United States.</p>











    
        
    



    
        
    




    




    


<figure>
    <div style="display: flex; gap: 10px;">
        <div style="flex: 1;">
            <p><strong>Before:</strong></p>
            <img src="https://kmcd.dev/posts/internet-map-2026/asia_before.svg" alt="Before" style="max-width: 100%; height: auto;">
        </div>
        <div style="flex: 1;">
            <p><strong>After:</strong></p>
            <img src="https://kmcd.dev/posts/internet-map-2026/asia_after.svg" alt="After" style="max-width: 100%; height: auto;">
        </div>
    </div>
    
    <figcaption>Asia on the map (<a href="https://map.kmcd.dev/?lat=17.6440&amp;lng=108.1494&amp;z=5.00&amp;year=2026&amp;cables=0&amp;peering=1&amp;ips=0" rel="external">before</a> and <a href="https://map.kmcd.dev/?lat=17.6440&amp;lng=108.1494&amp;z=5.00&amp;year=2026&amp;cables=0&amp;peering=1&amp;ips=1" rel="external">after</a> adding BGP data).</figcaption>
    
</figure>

<p>The physical meeting points of networks only tell us a part of the story. The global routing table reveals where address space is actually controlled and originated. Some cities carry significant weight without being major public peering hubs. The IP dominance layer makes that distinction visible.</p>











    
        
    



    
        
    




    




    


<figure>
    <div style="display: flex; gap: 10px;">
        <div style="flex: 1;">
            <p><strong>Before:</strong></p>
            <img src="https://kmcd.dev/posts/internet-map-2026/eu_before.svg" alt="Before" style="max-width: 100%; height: auto;">
        </div>
        <div style="flex: 1;">
            <p><strong>After:</strong></p>
            <img src="https://kmcd.dev/posts/internet-map-2026/eu_after.svg" alt="After" style="max-width: 100%; height: auto;">
        </div>
    </div>
    
    <figcaption>Europe on the map (<a href="https://map.kmcd.dev/?lat=48.9108&amp;lng=10.1420&amp;z=5.50&amp;year=2026&amp;cables=0&amp;peering=1&amp;ips=0" rel="external">before</a> and <a href="https://map.kmcd.dev/?lat=48.9108&amp;lng=10.1420&amp;z=5.50&amp;year=2026&amp;cables=0&amp;peering=1&amp;ips=1" rel="external">after</a> adding BGP data).</figcaption>
    
</figure>

<h4 id="the-chinese-internet">The Chinese Internet</h4>
<p>The Chinese internet is giant, but it presents a unique attribution challenge. Because so much of China’s domestic routing remains internal to national carriers, the global BGP table often only sees these massive networks when they peer at international hubs like Hong Kong, Los Angeles, or Frankfurt. An earlier version of my attribution code ended up adding all of China&rsquo;s IP space to these select few international hubs, which was clearly incorrect. It looked like China Telecom was the biggest ISP in Germany, which made it appear that China Telecom dominated Germany. It does not, at least not yet. To fix this, I implemented specific logic for China-based networks. I used pattern matching to parse provincial hints from APNIC WHOIS data. This mapped prefixes like <code>GD</code> or <code>SH</code> to their respective provincial capitals. I also linked ASNs to their parent organizations in PeeringDB to prevent Chinese networks from being misattributed to foreign exchange points. This resolved attribution for the vast majority of prefixes. Any remaining IP space attributed only at the country level is distributed across major domestic hubs.</p>
<p>The result is a far more realistic view of China’s internal internet topology.</p>
<h4 id="ghost-networks-and-spurious-asns">Ghost Networks and Spurious ASNs</h4>
<p>Not every entry in the global routing table represents a real network with a physical footprint. While investigating the data, I found several &ldquo;spurious&rdquo; Autonomous Systems that I had to filter out to keep the map accurate.</p>
<p>For example, I had to add safety checks to prevent &ldquo;IP swallowing.&rdquo; There is a massive <code>0.0.0.0/0</code> block often pinned to Australia in the APNIC database. Since <code>0.0.0.0/0</code> matches every single IPv4 address, that one entry would incorrectly claim the entire global IP space for Australia. I know they have a lot of open space down there, but that seemed excessive.</p>
<p>Another prominent example was the Department of Defense (DoD). The DoD holds several massive <code>/8</code> blocks (like <code>7.0.0.0/8</code> and <code>11.0.0.0/8</code>). While this space is technically routed, it does not represent commercial internet traffic. In early versions of my model, the registration data for these blocks linked them to administrative offices in New York City. This caused my script to dump millions of military IPs onto Manhattan and incorrectly made it look like the absolute center of the universe.</p>
<p>I also built a blocklist to ignore other non-geographic entities:</p>
<ul>
<li><strong>Administrative Containers</strong>: I filter out WHOIS entries containing <code>IANA-NETBLOCK</code>, <code>CIDR-BLOCK</code>, or <code>ERX-NETBLOCK</code>. These are typically placeholders for unassigned pools managed by regional registries rather than active networks.</li>
<li><strong>Registry Placeholders</strong>: Specific ASNs like 721, 56, and 37069 often function as loopbacks or registry tests.</li>
</ul>
<p>By explicitly ignoring these, the resulting map represents the actual commercial Internet rather than the administrative database of the Internet.</p>
<h3 id="ux-and-rendering">UX and Rendering</h3>
<p>In addition to adding more data to the map, I&rsquo;ve also made several improvements to the map itself.</p>
<h4 id="dynamic-cluster-grouping">Dynamic Cluster Grouping</h4>
<p>Layering BGP data onto an already complex physical map created a major design challenge: <strong>information density</strong>. With hundreds of new cities &ldquo;lighting up&rdquo; globally, the map became significantly cluttered when zoomed out.</p>
<p>To solve this, I implemented <strong>Dynamic Cluster Grouping</strong>. Close-by cities now group together into aggregate hubs at low zoom levels, which then split into individual markers as you zoom in. This isn&rsquo;t just a visual fix; by reducing the number of active SVG shapes in the DOM, it significantly improves panning performance on mobile devices.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">











    
        
    



    
        
    




    




    


<figure>
    <div style="display: flex; gap: 10px;">
        <div style="flex: 1;">
            <p><strong>Before:</strong></p>
            <img src="https://kmcd.dev/posts/internet-map-2026/clustering_no_hu_4806a84941c001de.png" alt="Before" style="max-width: 100%; height: auto;">
        </div>
        <div style="flex: 1;">
            <p><strong>After:</strong></p>
            <img src="https://kmcd.dev/posts/internet-map-2026/clustering_yes_hu_c54534966b7edd4e.png" alt="After" style="max-width: 100%; height: auto;">
        </div>
    </div>
    
    <figcaption>Before and after dynamic cluster groupings.</figcaption>
    
</figure>


    </span>

    
</div>

<p>Dynamic Cluster Grouping ensures the map remains legible, preventing the increased data density from overwhelming the map. When you click on a cluster, the details panel expands to list every city contained within that group.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">
    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/internet-map-2026/group-screenshot_hu_5f3d558220c9fb9.webp" class="center" width="500px"/>
    


    </span>

    
</div>

<h4 id="viewport-culling">Viewport Culling</h4>
<p>I also introduced <strong>Viewport Culling</strong>. The map now only renders assets currently within your bounds. As you pan to a new region, cities &ldquo;pop in&rdquo; dynamically, ensuring the browser isn&rsquo;t wasting resources on rendering things on the other side of the planet.</p>
<h4 id="updates-to-city-sizing">Updates to City Sizing</h4>
<p>The visual size of cities on the map also now dynamically reflects their importance. Previously, cities were sized based only on their relative peering bandwidth. Now, their size depends on a weighted combination of aggregate peering bandwidth and IP dominance, contributing 80% and 20% to the size calculation respectively. Although this ratio is arbitrary and was picked for aesthetic reasons, peering bandwidth is a stronger signal of real traffic concentration than raw IP space alone, so I think it should be emphasized significantly more.</p>
<h4 id="enabledisable-layers">Enable/disable layers</h4>
<p>Now, the map can be sliced into three layers: Cables, peering bandwidth, and IP allocations. There are controls that allow you to show or hide each of these layers individually.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">
    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/internet-map-2026/menu-screenshot_hu_7f429ea77cfc9d2.webp" class="center" width="500px"/>
    


    </span>

    
</div>

<h4 id="permalinks">Permalinks</h4>
<p>I also added permalinks to make the map state fully shareable. The URL now encodes the current latitude, longitude, zoom level, selected year, and active text filters. If you zoom into Southeast Asia in 2016 and search for &ldquo;Singapore&rdquo;, that exact view can be copied and shared. The resulting link will look like this:</p>
<blockquote>
<p><a href="https://map.kmcd.dev/?lat=3.1625&amp;lng=103.4033&amp;z=5.00&amp;year=2016&amp;q=singapore" rel="external">https://map.kmcd.dev/?lat=3.1625&lng=103.4033&z=5.00&year=2016&q=singapore</a></p>
</blockquote>
<p>&hellip;which will show exactly how <em>amazingly</em> connected Singapore is when others click on it.</p>
<h4 id="better-exports">Better Exports</h4>
<p>One of the most requested features for the map has been a way to export the current view for use in presentations, reports, posters, or just as a high-quality wallpaper.</p>
<p>Previously, I was using a standard Leaflet plugin for this, but it was not great. It would often fail in weird ways, leaving you with a glitched or incomplete rendering of the map. It also exported as PNG, which meant the beautiful vector data of the cables and cities was flattened into a low-resolution raster format.</p>
<p>Now there&rsquo;s a new export button that renders an isolated SVG. Because the map itself is built on SVGs, this new export method is lossless. It respects your current zoom level and position, allowing you to focus on a specific region and generate an incredibly high-quality vector file that you can scale to any size without losing a single pixel of detail. Most images in this post were generated using this new export feature.</p>
<h4 id="show-me-the-data">Show Me the Data</h4>
<p>Another one of the biggest requests I&rsquo;ve had in previous years is for access to the raw data behind the visualizations. For the 2026 edition, I have exposed the underlying JSON datasets that power the map. These files are curated from <strong>TeleGeography</strong> (for modern cables), <strong>PeeringDB</strong> (for IXPs), and historical data is curated from various sources including <strong>submarinenetworks.com</strong> and archived maps.</p>
<p>You can access these directly to build your own visualizations, analyze the growth of global bandwidth, or double check my numbers.</p>
<ul>
<li><a href="https://map.kmcd.dev/data/all_cables.json" rel="external"><code>all_cables.json</code></a>: <strong>The Core Map Data.</strong> A GeoJSON FeatureCollection containing all submarine cables. Each feature includes properties like <code>name</code>, <code>rfs_year</code> (Ready for Service), <code>decommission_year</code>, <code>owners</code>, and <code>landing_points</code>. This follows the standard <a href="https://geojson.org/" rel="external">GeoJSON format</a>.</li>
<li><a href="https://map.kmcd.dev/data/year-summaries.json" rel="external"><code>year-summaries.json</code></a>: Brief textual descriptions of notable events or milestones for specific years, displayed in the footer.</li>
<li><a href="https://map.kmcd.dev/data/city-dominance/2026.json" rel="external"><code>city-dominance/{year}.json</code></a>: Per-year JSON files (e.g., 2026.json) with detailed city-level peering capacity, regional information, and coordinates. Used for rendering city markers and calculating regional statistics.</li>
<li><a href="https://map.kmcd.dev/data/meta.json" rel="external"><code>meta.json</code></a>: Metadata including the minimum and maximum years covered by the visualization.</li>
</ul>
<h3 id="see-you-next-year">See You Next Year</h3>
<p>You might ask why I burned so much time manually attributing IP space when services like <a href="https://www.maxmind.com" rel="external">MaxMind</a> or <a href="https://ipinfo.io/" rel="external">IPInfo</a> already exist. The honest answer? Buying the data isn&rsquo;t fun. The joy of this project comes from the archaeology and the work involved in bringing order to chaotic and disjointed datasets and transforming them into something beautiful.</p>
<p>This was a great project, and I am extremely happy with the results. If you&rsquo;ve gotten this far without checking out the map, I&rsquo;m impressed with your restraint, but here&rsquo;s one more link for you to take a look: <strong><a href="https://map.kmcd.dev" rel="external">Explore the Map »</a></strong></p>
]]></content:encoded></item><item><title>Shell Log: Namaste</title><link>https://kmcd.dev/posts/shell-log-namaste/</link><pubDate>Mon, 16 Feb 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/shell-log-namaste/</guid><description><![CDATA[ 
                <p> <img hspace="5" src="https://kmcd.dev/posts/shell-log-namaste/cover.svg" /> </p>
                
                The &#39;Work-Life Balance&#39; Patch
                ]]></description><content:encoded><![CDATA[<p>We had a critical hotfix to deploy. The bug was losing the company $10k a minute. I just needed to run the deployment script. Unfortunately, the Sysadmin team had just rolled out the new mandatory &ldquo;Mental Health &amp; Wellness&rdquo; kernel module.</p>
<pre style="font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !important; background-color: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 6px; margin-bottom: 20px; font-size: 0.9rem; border: 1px solid #333; line-height: 1.5; white-space: pre-wrap; word-wrap: break-word; display: block;">
<span style="display: block; white-space: pre-wrap; margin: 0; padding: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !important;"><font color="#569cd6"><b><font color="#4ec9b0">marcus</font>@<font color="#ce9178">prod-deploy-01</font>:<font color="#dcdcaa">~/app</font>$</b></font>&nbsp;<font color="#d4d4d4">date</font></span>
<span style="display: block; white-space: pre-wrap; margin: 0; padding: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !important;"><font color="#cccccc">Fri May 21 17:01:02 PDT 2027</font></span>

<span style="display: block; white-space: pre-wrap; margin: 0; padding: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !important;"><font color="#569cd6"><b><font color="#4ec9b0">marcus</font>@<font color="#ce9178">prod-deploy-01</font>:<font color="#dcdcaa">~/app</font>$</b></font>&nbsp;<font color="#d4d4d4">./deploy.sh --force</font></span>
<span style="display: block; white-space: pre-wrap; margin: 0; padding: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !important;"><font color="#cccccc">
      .--.
    .'_\/_'.
    '. /\ .'   WellnessOS v2.0 (Stable)
      "||"     "Namaste, Admin."
       ||
    /\ || /\
   (  \||/  )
    \  ||  /
     ' -- '

[!] COMMAND BLOCKED
Reason: It is currently 17:01. Working hours ended at 17:00.
Suggestion: Go home. Hug your family. Touch grass.</font></span>

<span style="display: block; text-align: center; font-style: italic; background-color: #252525; border-radius: 20px; padding: 4px 20px; margin: 12px auto; width: fit-content; color: #aaa; font-size: 0.85rem; border: 1px solid #333; line-height: 1.4; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !important;">Are you kidding me? The site is down.</span>

<span style="display: block; white-space: pre-wrap; margin: 0; padding: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !important;"><font color="#569cd6"><b><font color="#4ec9b0">marcus</font>@<font color="#ce9178">prod-deploy-01</font>:<font color="#dcdcaa">~/app</font>$</b></font>&nbsp;<font color="#d4d4d4">sudo ./deploy_hotfix.sh --force</font></span>
<span style="display: block; white-space: pre-wrap; margin: 0; padding: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !important;"><font color="#cccccc">[sudo] password for marcus:
[!] SUDO DENIED
Analysis: The use of 'sudo' implies urgency, which creates cortisol.
Cortisol is bad for your long-term health.
Action: This incident has been logged with 'People Ops' as a potential burnout risk.</font></span>

<span style="display: block; text-align: center; font-style: italic; background-color: #252525; border-radius: 20px; padding: 4px 20px; margin: 12px auto; width: fit-content; color: #aaa; font-size: 0.85rem; border: 1px solid #333; line-height: 1.4; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !important;">I am going to throw this laptop out the window. Maybe I can trick it by changing the system time.</span>

<span style="display: block; white-space: pre-wrap; margin: 0; padding: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !important;"><font color="#569cd6"><b><font color="#4ec9b0">marcus</font>@<font color="#ce9178">prod-deploy-01</font>:<font color="#dcdcaa">~/app</font>$</b></font>&nbsp;<font color="#d4d4d4">sudo date -s "16:00:00"</font></span>
<span style="display: block; white-space: pre-wrap; margin: 0; padding: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !important;"><font color="#cccccc">[!] TIME MANIPULATION DETECTED
Analysis: Denial is the first stage of grief.
Action: Playing soothing ocean sounds via PC Speaker.
Locking terminal for 10 minutes for a mandatory 'Mindfulness Break'.</font></span>

<span style="display: block; text-align: center; font-style: italic; background-color: #252525; border-radius: 20px; padding: 4px 20px; margin: 12px auto; width: fit-content; color: #aaa; font-size: 0.85rem; border: 1px solid #333; line-height: 1.4; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !important;">It&rsquo;s actually beeping at me. This is insane.</span>

<span style="display: block; white-space: pre-wrap; margin: 0; padding: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !important;"><font color="#569cd6"><b><font color="#4ec9b0">marcus</font>@<font color="#ce9178">prod-deploy-01</font>:<font color="#dcdcaa">~/app</font>$</b></font>&nbsp;<font color="#d4d4d4">kill -9 -1</font></span>
<span style="display: block; white-space: pre-wrap; margin: 0; padding: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !important;"><font color="#cccccc">
[!] VIOLENT LANGUAGE DETECTED
The command 'kill' is on the banned words list for maintaining a positive workplace culture.
Please consider using non-violent alternatives such as:
  - 'transition-process'
  - 'release-to-universe'
  - 'conscious-uncoupling'

Action: Replacing your shell with 'eliza-therapy-bot'.

Hello, Marcus. I see you are trying to 'kill' something.
Do you feel like your father didn't listen to you enough?
> _</font></span>
</pre>
]]></content:encoded></item><item><title>HTTP/2 From Scratch: Part 1</title><link>https://kmcd.dev/posts/http2-from-scratch-part-1/</link><pubDate>Wed, 11 Feb 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/http2-from-scratch-part-1/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/http2-from-scratch-part-1/cover.svg" /> &lt;/p>
                
                Re-building the web in Go to learn more about it
                </description><content:encoded><![CDATA[<p>If you have ever opened a terminal and manually typed an HTTP/1.1 request, you know there is a certain beauty in its simplicity. You send a few lines of plain text and the server responds with more text. It is human-readable and easy to debug, but it is also remarkably inefficient for the modern web. If you haven&rsquo;t done that, I&rsquo;m hoping I can get you to use telnet this way for the first time today.</p>
<p>As applications have grown more complex and require many disparate streams of data, we seem to have reached the limit to what a text-based protocol can do. In this series, we are going to stop using the high-level abstractions provided by the Go standard library (mostly) and build an HTTP/2 implementation from the ground up. This is the way that I&rsquo;ve learned best, so maybe you can join me on this journey.</p>
<h3 id="the-simplicity-of-the-past">The Simplicity of the Past</h3>
<p>To understand why we need HTTP/2, we first have to look at how simple things used to be. You can still see the inner workings of the old web by using <code>telnet</code> or <code>nc</code> (netcat) to talk to a server. If you connect to port 80 and manually type a request, the interaction looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#bf616a">$ telnet kmcd.dev 80
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">Connected to kmcd.dev.
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">Escape character is &#39;^]&#39;.
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#88c0d0">GET</span> <span style="color:#8fbcbb">/</span> <span style="color:#81a1c1;font-weight:bold">HTTP</span><span style="color:#81a1c1">/</span><span style="color:#b48ead">1.1</span>
</span></span><span style="display:flex;"><span>Host<span style="color:#81a1c1">:</span> kmcd.dev
</span></span></code></pre></div><p>After hitting <code>enter</code> twice, the server sends back a text response (Some of the CloudFlare headers removed for brevity):</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">HTTP</span><span style="color:#81a1c1">/</span><span style="color:#b48ead">1.1</span> <span style="color:#b48ead">301</span> <span style="color:#bf616a">Moved Permanently</span>
</span></span><span style="display:flex;"><span>Date<span style="color:#81a1c1">:</span> Wed, 28 Jan 2026 14:17:33 GMT
</span></span><span style="display:flex;"><span>Content-Length<span style="color:#81a1c1">:</span> 0
</span></span><span style="display:flex;"><span>Connection<span style="color:#81a1c1">:</span> keep-alive
</span></span><span style="display:flex;"><span>Location<span style="color:#81a1c1">:</span> https://kmcd.dev/
</span></span><span style="display:flex;"><span>Server<span style="color:#81a1c1">:</span> cloudflare
</span></span></code></pre></div><p>This response is a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/301" rel="external">301 status code</a>, telling you to use the HTTPS version of the website, because plain-text HTTP is for heathens. But regardless, this is extremely insightful and cuts out many of the complexities that tooling can add. You can read every byte, understand every header, and even type the requests by hand. However, this transparency comes at a cost.</p>
<p>For completeness, here is a similar setup using HTTPS via the openssl CLI client:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ openssl s_client -connect kmcd.dev:443 -servername kmcd.dev
</span></span></code></pre></div><details >
    <summary>
        openssl command output (click to expand)</summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#bf616a">CONNECTED(00000006)
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R4
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">verify return:1
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">depth=1 C = US, O = Google Trust Services, CN = WE1
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">verify return:1
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">depth=0 CN = kmcd.dev
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">verify return:1
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">write W BLOCK
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">---
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">Certificate chain
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"> 0 s:/CN=kmcd.dev
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">   i:/C=US/O=Google Trust Services/CN=WE1
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"> 1 s:/C=US/O=Google Trust Services/CN=WE1
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">   i:/C=US/O=Google Trust Services LLC/CN=GTS Root R4
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"> 2 s:/C=US/O=Google Trust Services LLC/CN=GTS Root R4
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">   i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">---
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">Server certificate
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">-----BEGIN CERTIFICATE-----
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">MIIDkDCCAzWgAwIBAgIRANuMVwoYX/O/E75qT9V5T9AwCgYIKoZIzj0EAwIwOzEL
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">MAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczEMMAoG
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">A1UEAxMDV0UxMB4XDTI2MDEwMTA1MjA0OVoXDTI2MDQwMTA2MjA0M1owEzERMA8G
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">A1UEAxMIa21jZC5kZXYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATxkKGuge8n
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">FcjibwoBq1QKBeq/KDwkorBy7MUuyYMcvhfdB9QGMalqF8wDtkruPStPe6rMAUjZ
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">NAoDSKmJwrdco4ICQDCCAjwwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsG
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">AQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFKVQVjLBxQ3apOR8KM1a/BjG
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">BNyzMB8GA1UdIwQYMBaAFJB3kjVnxP+ozKnme9mAeXvMk/k4MF4GCCsGAQUFBwEB
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">BFIwUDAnBggrBgEFBQcwAYYbaHR0cDovL28ucGtpLmdvb2cvcy93ZTEvMjR3MCUG
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">CCsGAQUFBzAChhlodHRwOi8vaS5wa2kuZ29vZy93ZTEuY3J0MBMGA1UdEQQMMAqC
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">CGttY2QuZGV2MBMGA1UdIAQMMAowCAYGZ4EMAQIBMDYGA1UdHwQvMC0wK6ApoCeG
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">JWh0dHA6Ly9jLnBraS5nb29nL3dlMS9mLUxxQnNPOTBnWS5jcmwwggEDBgorBgEE
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">AdZ5AgQCBIH0BIHxAO8AdQDRbqmlaAd+ZjWgPzel3bwDpTxBEhTUiBj16TGzI8uV
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">BAAAAZt4N1MJAAAEAwBGMEQCIFpnx0F4+HFZiAAZp/S1OXTqUGE2XXEJreljtBiX
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">52ezAiA8IqlGlvYzT9to8aWkfCmvd5/zxpktLVxjPAZhTtvO/QB2AA5XlLzzrqk+
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">MxssmQez95Dfm8I9cTIl3SGpJaxhxU4hAAABm3g3Ui8AAAQDAEcwRQIgYpHRsDzn
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">0EyvJeasmiFJHnVAgNjO9nVLhHUkgMMluQ0CIQCLxOjYLJUGwBMAF4RG2L7J+P/A
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">4g2ojvjXSOqUhIpJmTAKBggqhkjOPQQDAgNJADBGAiEAgfuMNfYBAbPoYqIJ7TyY
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">y1uNp7kzpQShTsBpjgQrqTsCIQC22/6iBI+qiTSgWIrF7tKFil8+XyoMdmf8CTeI
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">GBAV/w==
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">-----END CERTIFICATE-----
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">subject=/CN=kmcd.dev
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">issuer=/C=US/O=Google Trust Services/CN=WE1
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">---
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">No client certificate CA names sent
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">Server Temp Key: ECDH, X25519, 253 bits
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">---
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">SSL handshake has read 2789 bytes and written 368 bytes
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">---
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">New, TLSv1/SSLv3, Cipher is AEAD-CHACHA20-POLY1305-SHA256
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">Server public key is 256 bit
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">Secure Renegotiation IS NOT supported
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">Compression: NONE
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">Expansion: NONE
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">No ALPN negotiated
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">SSL-Session:
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">    Protocol  : TLSv1.3
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">    Cipher    : AEAD-CHACHA20-POLY1305-SHA256
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">    Session-ID:
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">    Session-ID-ctx:
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">    Master-Key:
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">    Start Time: 1769674847
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">    Timeout   : 7200 (sec)
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">    Verify return code: 0 (ok)
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">---
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#88c0d0">GET</span> <span style="color:#8fbcbb">/</span> <span style="color:#81a1c1;font-weight:bold">HTTP</span><span style="color:#81a1c1">/</span><span style="color:#b48ead">1.1</span>
</span></span><span style="display:flex;"><span>Host<span style="color:#81a1c1">:</span> kmcd.dev
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>HTTP/1.1 200 OK
</span></span><span style="display:flex;"><span>Date: Thu, 29 Jan 2026 08:20:53 GMT
</span></span><span style="display:flex;"><span>Content-Type: text/html; charset=utf-8
</span></span><span style="display:flex;"><span>Transfer-Encoding: chunked
</span></span><span style="display:flex;"><span>Connection: keep-alive
</span></span><span style="display:flex;"><span>Access-Control-Allow-Origin: *
</span></span><span style="display:flex;"><span>Cache-Control: max-age=31536000, public
</span></span><span style="display:flex;"><span>cf-cache-status: DYNAMIC
</span></span><span style="display:flex;"><span>Link: &lt;https://fonts.googleapis.com&gt;; rel=&#34;preconnect&#34;
</span></span><span style="display:flex;"><span>referrer-policy: no-referrer
</span></span><span style="display:flex;"><span>x-content-type-options: nosniff
</span></span><span style="display:flex;"><span>x-frame-options: deny
</span></span><span style="display:flex;"><span>x-xss-protection: 1; mode=block
</span></span><span style="display:flex;"><span>Vary: accept-encoding
</span></span><span style="display:flex;"><span>Server: cloudflare
</span></span><span style="display:flex;"><span>alt-svc: h3=&#34;:443&#34;; ma=86400
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>3d8d
</span></span><span style="display:flex;"><span>&lt;!doctype html&gt;&lt;html lang=en&gt;&lt;head&gt;&lt;meta name=generator content=&#34;Hugo 0.152.2&#34;&gt;...[snip]...&lt;/head&gt;&lt;/html&gt;
</span></span><span style="display:flex;"><span>0
</span></span></code></pre></div>
</details>
<p>The response has been trimmed for brevity, but this shows that you can use a TLS-wrapped socket to access HTTPS websites using a plain-text interface similar to telnet. This shows how the &ldquo;S&rdquo; in &ldquo;HTTPS&rdquo; is simply HTTP wrapped with TLS.</p>
<h3 id="head-of-line-blocking">Head-of-line blocking</h3>
<p>The primary issue with HTTP/1.1 is <strong>head-of-line blocking</strong>. Even if you have a high-speed fiber connection, a single TCP stream can only process one request at a time. If your browser needs a tiny CSS file but it is stuck behind a massive high-resolution image, that CSS file simply waits. In the early days of the web, when pages were just a few kilobytes of text and a couple of images, this was a minor inconvenience. Today, the average webpage loads hundreds of individual assets.</p>
<p>Web developers and browser makers are clever individuals. They fixed this by opening multiple TCP connections to the same server. Browsers usually allow up to six parallel connections per domain name. If developers need more than that, they would host assets on different domain names using a technique called domain sharding. This set of techniques did make loading web assets faster, but this is a heavy-handed solution that comes as a cost. Each new connection requires a full TCP handshake and a TLS negotiation which means needing more powerful servers and clients to deal with this extra crypto negotiation.</p>
<p>Beyond the connection limits, HTTP/1.1 is incredibly verbose. Every single request sends the same headers over and over again. Your user agent and cookie headers are almost identical for every image on a page, yet the protocol forces you to upload that redundant text hundreds of times. On mobile networks with limited upload bandwidth, this overhead becomes a measurable performance tax.</p>
<h3 id="persistent-connections-and-pipelining">Persistent Connections and Pipelining</h3>
<p>Before we dive into the binary world of HTTP/2, it’s worth noting that HTTP/1.1 didn’t just sit idly by while performance suffered. It introduced two key concepts to mitigate the overhead of opening new connections: Persistent Connections (Keep-Alive) and Pipelining.</p>
<p>In the original HTTP/1.0 days, every single request required a brand-new TCP handshake. HTTP/1.1 changed the default behavior to &ldquo;Keep-Alive,&rdquo; allowing a single TCP connection to stay open for multiple subsequent requests. This saved the &ldquo;tax&rdquo; of the initial handshake for every image or script.</p>
<p>Then came Pipelining. The idea was brilliant on paper: instead of waiting for a response before sending the next request, a client could fire off three or four requests in a row. While you could send requests in a batch, the server was strictly required to send the responses back in the exact same order they were received. If the first request in the &ldquo;pipe&rdquo; was a slow database query and the second was a tiny static file, the static file was still stuck waiting for the database; the classic &ldquo;Head-of-Line Blocking&rdquo; problem. Because of buggy implementations in middleware and browsers, pipelining never saw widespread adoption and is disabled by default in almost every modern browser for <code>HTTP/1</code>. It was a valiant attempt at concurrency that ultimately proved the need for a more radical shift in how we frame data.</p>
<h3 id="entering-the-binary-world">Entering the Binary World</h3>
<p>HTTP/2 solves these problems by moving away from text entirely. As defined in <a href="https://www.rfc-editor.org/rfc/rfc9113.html#name-http-frames" rel="external">RFC 9113, Section 4</a>, it introduces a binary framing layer that allows us to interleave multiple requests and responses over a single connection. This technique is called multiplexing. By breaking every message into small, independent frames, a slow request no longer blocks the ones behind it. The protocol can send a piece of an image, then a piece of a script, then another piece of the image, all within the same stream.</p>
<p>In this binary world, we stop thinking about lines of text and start thinking about frames. Every piece of data is wrapped in a header that tells the receiver exactly what it is, how big it is, and which request it belongs to. This makes parsing significantly faster for machines. In HTTP/1.1, the server has to read the text and look for newlines to know where a header ends. In HTTP/2, the server knows exactly how many bytes to read next.</p>
<h3 id="negotiating-the-connection">Negotiating the Connection</h3>
<p>Since the web is built on backward compatibility, a client needs a way to tell a server it wants to use HTTP/2 without breaking things for older servers. We do this using ALPN (Application-Layer Protocol Negotiation) during the TLS handshake. This allows the client and server to agree on a protocol before they ever start sending application data.</p>
<p>Once the encrypted tunnel is established, the client must send a connection preface. This is a 24-byte magic string that serves as a final confirmation. The string looks like this, as documented in <a href="https://www.rfc-editor.org/rfc/rfc9113.html#name-http-2-connection-preface" rel="external">Section 3.5 in RFC-9113</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#bf616a">PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
</span></span></span></code></pre></div><p>If the server receives this string successfully, it will speak HTTP/2 from then on. It will respond with its own settings. If it does not, it will likely drop the connection. This prevents a modern client from sending binary data to an old server that is still expecting plain text.</p>
<h3 id="the-implementation-in-go">The Implementation in Go</h3>
<p>The following script is the starting point for our project. It uses the <code>crypto/tls</code> package to handle the encryption but stops right at the moment the HTTP/2 state machine should take over. We define the mandatory preface and configure our TLS dialer to negotiate for the &ldquo;h2&rdquo; protocol.</p>
<details >
    <summary>
        go/client.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http2-from-scratch-part-1/go/client.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;crypto/tls&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">const</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// The &#34;Magic&#34; preface required by RFC 9113</span>
</span></span><span style="display:flex;"><span>	preface <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n&#34;</span>
</span></span><span style="display:flex;"><span>	server  <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;kmcd.dev:443&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// We configure TLS to specifically look for &#34;h2&#34; via ALPN</span>
</span></span><span style="display:flex;"><span>	config <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>tls<span style="color:#eceff4">.</span>Config<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		NextProtos<span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#34;h2&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Dial the server and perform the handshake</span>
</span></span><span style="display:flex;"><span>	conn<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> tls<span style="color:#eceff4">.</span><span style="color:#88c0d0">Dial</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;tcp&#34;</span><span style="color:#eceff4">,</span> server<span style="color:#eceff4">,</span> config<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to connect: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Ensure the server actually agreed to speak HTTP/2</span>
</span></span><span style="display:flex;"><span>	state <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">ConnectionState</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#34;h2&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Server did not negotiate HTTP/2: %s&#34;</span><span style="color:#eceff4">,</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Connected to %s using %s\n&#34;</span><span style="color:#eceff4">,</span> server<span style="color:#eceff4">,</span> state<span style="color:#eceff4">.</span>NegotiatedProtocol<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Send the Connection Preface to initialize the H2 session</span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>preface<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Failed to send preface: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Preface sent successfully. The connection is open.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<p>This is actually quite amazing. A lot of hard work is being done to give us a TLS-wrapped connection and we&rsquo;ve also used TLS to negotiate the HTTP/2 protocol for us. TLS is a complex protocol in its own right, so we’ll rely on Go’s <code>crypto/tls</code> package and focus entirely on <code>HTTP/2</code> from this point forward.</p>
<h3 id="what-happens-next">What Happens Next</h3>
<p>At this point, the connection is technically alive, but it is silent. The server is now expecting us to handle binary frames. If you were to run this code and try to read from the connection, you would see a stream of bytes representing the server&rsquo;s initial configuration.</p>
<p>In the next post, we leave the comfort of strings behind and dive into the <code>encoding/binary</code> package. We’ll build a parser for the mandatory 9-byte frame header, learn how to mask bits, and start interpreting the hex constants that define the language of the modern web.</p>
]]></content:encoded></item><item><title>IRC Log: Standup</title><link>https://kmcd.dev/posts/irc-log-standup/</link><pubDate>Mon, 09 Feb 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/irc-log-standup/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/irc-log-standup/cover.svg" /> &lt;/p>
                
                The lead is away, so the team holds standup in IRC. It quickly descends into abstract art and magenta-colored login pages.
                </description><content:encoded><![CDATA[<p>The lead is at a conference, so standup is happening in <code>#engineering-team</code>. It went about as well as expected.</p>
<div class="chat-log" style="font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; background-color: #222; color: #eee; padding: 15px; border-radius: 5px; overflow-x: auto; line-height: 1.4; margin-bottom: 20px; font-size: 0.9rem; border: 1px solid #444;">


<div>
  <font color="#777777">[09:00]</font> 
  <font color="#4caf50"><b>*** Topic: Daily Standup | Lead is OOO at GopherCon</b></font>
</div>






<div>
  <font color="#777777">[09:01]</font> 
  <font color="#d4e157"><b>&lt;senior_dev&gt;</b></font>
  <span>Morning everyone. Since Dave is at the conference, let&rsquo;s just do a quick text-based standup here. Keep it brief.</span>
</div>






<div>
  <font color="#777777">[09:02]</font> 
  <font color="#ef5350"><b>&lt;junior_dev&gt;</b></font>
  <span>Yesterday: Fixed the CSS bug on the login page. Today: Investigating why the login page is now magenta. No blockers.</span>
</div>






<div>
  <font color="#777777">[09:03]</font> 
  <font color="#29b6f6"><b>&lt;backend_ben&gt;</b></font>
  <span>Yesterday: Optimized the API query. Today: Realizing I optimized the wrong API. Redoing it. Blocker: My coffee machine is leaking.</span>
</div>






<div>
  <font color="#777777">[09:04]</font> 
  <font color="#26a69a"><b>&lt;qa_queen&gt;</b></font>
  <span>Yesterday: Found 14 bugs in Ben&rsquo;s &ldquo;optimized&rdquo; API. Today: Testing the magenta login page. It&rsquo;s&hellip; a choice.</span>
</div>






<div>
  <font color="#777777">[09:05]</font> 
  <font color="#ef5350"><b>&lt;junior_dev&gt;</b></font>
  <span>it&rsquo;s a bold choice!</span>
</div>






<div>
  <font color="#777777">[09:05]</font> 
  <font color="#d4e157"><b>&lt;senior_dev&gt;</b></font>
  <span>Yesterday: PR reviews. Today: Dealing with the magenta crisis and Ben&rsquo;s wet kitchen. Blocker: None.</span>
</div>






<div>
  <font color="#777777">[09:06]</font> 
  <font color="#ab47bc"><b>&lt;intern_ian&gt;</b></font>
  <span>Yesterday: Learned how to use <code>git merge</code>. Today: Learned how to use <code>git reset --hard</code>. Blocker: The repo is currently in a state that I can only describe as &ldquo;abstract art.&rdquo;</span>
</div>







<div>
  <font color="#777777">[09:07]</font> 
  
    <font color="#e040fb"><i>* <font color="#42a5f5"><b>senior_dev</b></font> checks the commit log</i></font>
  
</div>






<div>
  <font color="#777777">[09:07]</font> 
  <font color="#d4e157"><b>&lt;senior_dev&gt;</b></font>
  <span>Ian, why is there a commit titled &ldquo;whoopsie&rdquo;?</span>
</div>






<div>
  <font color="#777777">[09:08]</font> 
  <font color="#ab47bc"><b>&lt;intern_ian&gt;</b></font>
  <span>it seemed descriptive at the time</span>
</div>






<div>
  <font color="#777777">[09:10]</font> 
  <font color="#26a69a"><b>&lt;qa_queen&gt;</b></font>
  <span>I&rsquo;ve found another bug. If you click the magenta button 50 times, it plays the Nyan Cat theme. Anyway, I&rsquo;m just sitting here waiting for the next set of bugs to drop—I mean, features.</span>
</div>






<div>
  <font color="#777777">[09:10]</font> 
  <font color="#ef5350"><b>&lt;junior_dev&gt;</b></font>
  <span>that&rsquo;s not a bug, that&rsquo;s an engagement feature.</span>
</div>






<div>
  <font color="#777777">[09:11]</font> 
  <font color="#d4e157"><b>&lt;senior_dev&gt;</b></font>
  <span>Okay, standup over. Ben, fix your API. Ian, don&rsquo;t touch anything. Kevin, change the color back.</span>
</div>






<div>
  <font color="#777777">[09:12]</font> 
  <font color="#ef5350"><b>&lt;junior_dev&gt;</b></font>
  <span>but the vibes are so good right now</span>
</div>


<div>
  <font color="#777777">[09:13]</font> 
  <font color="#4caf50"><b>*** senior_dev is now known as senior_dev_drinking_early</b></font>
</div>


</div>

]]></content:encoded></item><item><title>HTTP/1.1 From Scratch</title><link>https://kmcd.dev/posts/http1.1-from-scratch/</link><pubDate>Wed, 04 Feb 2026 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/http1.1-from-scratch/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/http1.1-from-scratch/cover.svg" /> &lt;/p>
                
                When the web became whole.
                </description><content:encoded><![CDATA[<p>HTTP/1.1 has become synonymous with &ldquo;HTTP/1&rdquo; because it made several steps towards enabling the scale that the web was starting to experience in the late 1990s. It took the foundational concepts we explored in <a href="https://kmcd.dev/posts/http0.9-from-scratch">HTTP/0.9</a> and <a href="https://kmcd.dev/posts/http1.0-from-scratch">HTTP/1.0</a> and made several advancements and adjustments that enable the web to scale for almost two decades.</p>
<p>While newer protocols like <code>HTTP/2</code> and <code>HTTP/3</code> have since arrived with their own improvements, HTTP/1.1 remains a non-negotiable requirement&hellip; well, almost. There are a few who believe that it is time to <a href="https://http1mustdie.com/" rel="external">kill HTTP/1.1</a> and some believe it would immediately <a href="https://markmcb.com/web/selectively_disabling_http_1/" rel="external">reduce bot traffic</a>. It has historically been the default transport for the web and the protocol that servers and clients fall back on. Its simplicity and power are why, even now, a massive portion of internet traffic flows over HTTP/1.1.</p>
<p>Let&rsquo;s start looking at the new features in HTTP/1.1 that made it a such a sturdy pillar of the web for so long and then build a server in Go that implements them from scratch.</p>
<h2 id="improvements-over-http10">Improvements over HTTP/1.0</h2>
<p>HTTP/1.1 wasn&rsquo;t just a bump in the version number; it was a targeted response to the scaling bottlenecks of the 90s. It introduced specific features to cut down on connection overhead and handle dynamic data more efficiently than 1.0 ever could.</p>
<h3 id="persistent-connections-keep-alive">Persistent Connections (Keep-Alive)</h3>
<p>This is probably the most important performance improvement in HTTP/1.1. In HTTP/1.0, every single request required a new, separate TCP connection. Setting up a TCP connection is a multi-step handshake process that introduces significant latency. For a typical website that requires dozens of resources (CSS, JavaScript, images), this overhead added up quickly. And this was before the web was encrypted, so wrapping requests in TLS for &ldquo;secure pages&rdquo; would add two more round trips too this.</p>
<p>HTTP/1.1 introduced persistent connections by default. This allows the browser to send multiple requests over a single TCP connection, eliminating the repeated connection setup cost.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">

    
    
        
        
        <img src="https://kmcd.dev/posts/http1.1-from-scratch/http-versions.svg" class="center" width="800px"/>
    



    </span>

    
</div>

<p>A client or server can signal that they wish to close the connection after a request by sending the <code>Connection: close</code> header. Otherwise, the connection is assumed to be &ldquo;kept alive&rdquo;.</p>
<h3 id="the-host-header-enabling-virtual-hosting">The <code>Host</code> Header: Enabling Virtual Hosting</h3>
<p>In the early web, a server at a specific IP address hosted a single website. As the web grew, this became incredibly inefficient. The <code>Host</code> header, which became mandatory in HTTP/1.1, solved this. It specifies the domain name of the server the client is trying to reach. This allowed a single server (with a single IP address) to host hundreds or thousands of different websites, a practice known as virtual hosting. This was a critical innovation for the economic scalability of web hosting.</p>
<h3 id="chunked-transfer-encoding">Chunked Transfer Encoding</h3>
<p>Before HTTP/1.1, for a server to send a response, it had to know its exact size beforehand to set the <code>Content-Length</code> header. This was fine for static files but a major problem for dynamically generated content. What if you were streaming a large video or generating a big HTML page on the fly? You&rsquo;d have to buffer the entire response in memory just to calculate its size.</p>
<p>Chunked Transfer Encoding elegantly solves this. The server can send the response body in a series of &ldquo;chunks.&rdquo; Each chunk is prefixed with its size in hexadecimal, followed by the chunk data itself. The stream is terminated by a final chunk of size 0.</p>
<p>Here&rsquo;s what a chunked response looks like on the wire:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">HTTP</span><span style="color:#81a1c1">/</span><span style="color:#b48ead">1.1</span> <span style="color:#b48ead">200</span> <span style="color:#bf616a">OK</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#81a1c1">:</span> text/plain
</span></span><span style="display:flex;"><span>Transfer-Encoding<span style="color:#81a1c1">:</span> chunked
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#b48ead">8</span><span style="color:#ebcb8b">\r\n</span>
</span></span><span style="display:flex;"><span>kmcd.dev<span style="color:#ebcb8b">\r\n</span>
</span></span><span style="display:flex;"><span><span style="color:#b48ead">12</span><span style="color:#ebcb8b">\r\n</span>
</span></span><span style="display:flex;"><span> is awesome!<span style="color:#ebcb8b">\r\n</span>
</span></span><span style="display:flex;"><span><span style="color:#b48ead">0</span><span style="color:#ebcb8b">\r\n</span>
</span></span><span style="display:flex;"><span><span style="color:#ebcb8b">\r\n</span>
</span></span></code></pre></div><p>This allowed for much more efficient handling of dynamic content and laid the groundwork for streaming media.</p>
<h3 id="modern-caching-with-cache-control">Modern Caching with <code>Cache-Control</code></h3>
<p>HTTP/1.0 had basic caching headers, but HTTP/1.1 introduced the powerful <code>Cache-Control</code> header. This gave developers fine-grained control over how browsers and intermediate proxies cache resources. Directives like <code>max-age</code>, <code>public</code>, <code>private</code>, <code>no-cache</code>, and <code>no-store</code> allowed for sophisticated caching strategies, dramatically reducing bandwidth usage and improving load times.</p>
<p>A quick look at my own site&rsquo;s headers shows this in action:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl --http1.1 -I https://kmcd.dev
</span></span><span style="display:flex;"><span>HTTP/1.1 <span style="color:#b48ead">200</span> OK
</span></span><span style="display:flex;"><span>Date: Sat, <span style="color:#b48ead">24</span> Jan <span style="color:#b48ead">2026</span> 10:12:55 GMT
</span></span><span style="display:flex;"><span>Content-Type: text/html<span style="color:#eceff4">;</span> charset<span style="color:#81a1c1">=</span>utf-8
</span></span><span style="display:flex;"><span>Connection: keep-alive
</span></span><span style="display:flex;"><span>Cache-Control: max-age<span style="color:#81a1c1">=</span>31536000, public
</span></span><span style="display:flex;"><span>Server: cloudflare
</span></span></code></pre></div><p>This tells any browser or CDN that it&rsquo;s safe to cache this response for a full year, which is great for performance.</p>
<h3 id="more-methods-for-restful-apis">More Methods for RESTful APIs</h3>
<p>HTTP/1.0 was primarily about <code>GET</code>, <code>POST</code>, and <code>HEAD</code>. HTTP/1.1 expanded the vocabulary of the web by adding new methods that were crucial for the development of RESTful APIs:</p>
<ul>
<li><strong>PUT:</strong> Create or replace a resource at a given URI.</li>
<li><strong>PATCH:</strong> Partially modify a resource.</li>
<li><strong>DELETE:</strong> Delete a resource at a given URI.</li>
<li><strong>CONNECT:</strong> Used for creating tunnels, most notably for HTTPS through proxies.</li>
<li><strong>OPTIONS:</strong> Describe the communication options for the target resource.</li>
<li><strong>TRACE:</strong> Performs a message loop-back test along the path to the target resource.</li>
</ul>
<h3 id="100-continue-status-code"><code>100 Continue</code> Status Code</h3>
<p>When a client needs to send a large request body (like uploading a file), it can be inefficient to send the entire payload only for the server to reject it (e.g., due to authentication failure or size limits). The <code>100 Continue</code> status code provides a solution.</p>
<p>The client can send the request headers with the <code>Expect: 100-continue</code> header and then wait.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#88c0d0">PUT</span> <span style="color:#8fbcbb">/images</span> <span style="color:#81a1c1;font-weight:bold">HTTP</span><span style="color:#81a1c1">/</span><span style="color:#b48ead">1.1</span>
</span></span><span style="display:flex;"><span>Host<span style="color:#81a1c1">:</span> images.example.com
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#81a1c1">:</span> image/png
</span></span><span style="display:flex;"><span>Content-Length<span style="color:#81a1c1">:</span> 500000
</span></span><span style="display:flex;"><span>Expect<span style="color:#81a1c1">:</span> 100-continue
</span></span></code></pre></div><p>If the server is willing to accept the request, it responds with <code>HTTP/1.1 100 Continue</code>. The client then proceeds to send the request body. If the server is not going to accept it, it can immediately send a final error code like <code>413 Payload Too Large</code>, and the client knows not to waste bandwidth sending the body.</p>
<h2 id="building-a-simple-http11-server-in-go">Building a Simple HTTP/1.1 Server in Go</h2>
<p>Now for the fun part. Let&rsquo;s build a server that understands these new features. We&rsquo;ll be pulling from the complete server code found here: <a href="https://github.com/sudorandom/kmcd.dev/blob/main/content/posts/2026/http1.1-from-scratch/go/server/main.go" target="_blank">main.go</a>.</p>
<h3 id="handling-persistent-connections">Handling Persistent Connections</h3>
<p>To support keep-alive, our connection handler can&rsquo;t just handle one request and then close the connection. It needs to loop, processing multiple requests on the same connection until the client or server decides to close it.</p>
<p>The structure of our server looks like this: <code>ListenAndServe</code> accepts new TCP connections and spins up a <code>handleConnection</code> goroutine for each one.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ListenAndServe starts the server.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>s <span style="color:#81a1c1">*</span>Server<span style="color:#eceff4">)</span> <span style="color:#88c0d0">ListenAndServe</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// ... listener setup ...</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> l<span style="color:#eceff4">.</span><span style="color:#88c0d0">Accept</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">go</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> s<span style="color:#eceff4">.</span><span style="color:#88c0d0">handleConnection</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Error</span><span style="color:#eceff4">(</span>fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sprintf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;http error: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Now let&rsquo;s look at the new <code>handleConnection</code> method. It contains an infinite loop that repeatedly calls <code>handleRequest</code>. Previously, this method didn&rsquo;t exist because in HTTP/1.0 we only ever handled a single request per connection. The loop only exits if there&rsquo;s a fatal error (timeout, protocol error, etc.) or if <code>handleRequest</code> signals that the connection should be closed like when the user sends a request with the header <code>Connection: close</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>s <span style="color:#81a1c1">*</span>Server<span style="color:#eceff4">)</span> <span style="color:#88c0d0">handleConnection</span><span style="color:#eceff4">(</span>conn net<span style="color:#eceff4">.</span>Conn<span style="color:#eceff4">)</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// handleRequest does the work of reading and responding</span>
</span></span><span style="display:flex;"><span>		shouldClose<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> s<span style="color:#eceff4">.</span><span style="color:#88c0d0">handleRequest</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#616e87;font-style:italic">// io.EOF is a normal way for a persistent connection to end.</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">Is</span><span style="color:#eceff4">(</span>err<span style="color:#eceff4">,</span> io<span style="color:#eceff4">.</span>EOF<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> shouldClose <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#616e87;font-style:italic">// Client requested a close, so we exit the loop.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Inside <code>handleRequest</code>, we determine if the connection should be closed by inspecting the <code>Connection</code> header.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Default to keeping the connection alive for HTTP/1.1</span>
</span></span><span style="display:flex;"><span>req<span style="color:#eceff4">.</span>Close <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">false</span> 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Check if the client or a previous handler wants to close the connection.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">switch</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">ToLower</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span><span style="color:#88c0d0">Get</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Connection&#34;</span><span style="color:#eceff4">))</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">case</span> <span style="color:#a3be8c">&#34;close&#34;</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>    req<span style="color:#eceff4">.</span>Close <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">case</span> <span style="color:#a3be8c">&#34;keep-alive&#34;</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// This is the default, but we handle it explicitly.</span>
</span></span><span style="display:flex;"><span>    req<span style="color:#eceff4">.</span>Close <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><h3 id="requiring-the-host-header">Requiring the <code>Host</code> Header</h3>
<p>This is a simple but crucial part of our server. After parsing the headers, we just check for the presence of the <code>Host</code> header. If it&rsquo;s missing, we return an error and close the connection.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> req<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">[</span><span style="color:#a3be8c">&#34;Host&#34;</span><span style="color:#eceff4">];</span> <span style="color:#eceff4">!</span>ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// We send an error response here in a real implementation.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;required &#39;Host&#39; header not found&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><h3 id="handling-chunked-bodies">Handling Chunked Bodies</h3>
<p>This is the most complex part. We need to be able to both read a chunked request body from a client and send a chunked response.</p>
<h4 id="reading-a-chunked-request">Reading a Chunked Request</h4>
<p>When we parse the request headers, if we see <code>Transfer-Encoding: chunked</code>, we know we can&rsquo;t just use <code>io.LimitReader</code> with <code>Content-Length</code>. Instead, we need a special reader. Our <code>chunkedBodyReader</code> does just this.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> chunkedBodyReader <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	reader <span style="color:#81a1c1">*</span>bufio<span style="color:#eceff4">.</span>Reader
</span></span><span style="display:flex;"><span>	n      <span style="color:#81a1c1">int64</span> <span style="color:#616e87;font-style:italic">// bytes left in current chunk</span>
</span></span><span style="display:flex;"><span>	err    <span style="color:#81a1c1">error</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>chunkedBodyReader<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Read</span><span style="color:#eceff4">(</span>p <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>n <span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> err <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Do we need to read the size of the next chunk?</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>n <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		r<span style="color:#eceff4">.</span>n<span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>err <span style="color:#eceff4">=</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">readChunkSize</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// If the next chunk size is 0, we&#39;re at the end.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>n <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> io<span style="color:#eceff4">.</span>EOF
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// ... logic to read from the current chunk ...</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>The <code>readChunkSize</code> method is responsible for reading a line, parsing the hexadecimal chunk size, and preparing the reader to consume that many bytes.</p>
<h4 id="sending-a-chunked-response">Sending a Chunked Response</h4>
<p>On the response side, things are even cooler. If our <code>http.ResponseWriter</code> implementation doesn&rsquo;t have a <code>Content-Length</code> set when <code>WriteHeader</code> is called, we can automatically switch to using chunked encoding.</p>
<p>Our <code>responseBodyWriter</code> checks for this condition.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">writeHeader</span><span style="color:#eceff4">(</span>conn io<span style="color:#eceff4">.</span>Writer<span style="color:#eceff4">,</span> proto <span style="color:#81a1c1">string</span><span style="color:#eceff4">,</span> headers http<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">,</span> statusCode <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> clSet <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">[</span><span style="color:#a3be8c">&#34;Content-Length&#34;</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> teSet <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">[</span><span style="color:#a3be8c">&#34;Transfer-Encoding&#34;</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// If no length is set, we decide to use chunking.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>clSet <span style="color:#81a1c1">&amp;&amp;</span> <span style="color:#eceff4">!</span>teSet <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		r<span style="color:#eceff4">.</span>chunkedEncoding <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>		r<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">.</span><span style="color:#88c0d0">Set</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Transfer-Encoding&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;chunked&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// ... write headers ...</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Then, the <code>Write</code> method, if <code>chunkedEncoding</code> is true, will write each chunk with the required formatting.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>b <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// ... ensure headers are written ...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>chunkedEncoding <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Write the chunk size in hex, followed by \r\n</span>
</span></span><span style="display:flex;"><span>		chunkSize <span style="color:#81a1c1">:=</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sprintf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;%x\r\n&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>chunkSize<span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Write the actual chunk data</span>
</span></span><span style="display:flex;"><span>	n<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> n<span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>chunkedEncoding <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Write the trailing \r\n for the chunk</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>nlcf<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> n<span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> n<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Finally, after the last <code>Write</code> call, we send the terminal <code>0\r\n\r\n</code> chunk to signal the end of the response.</p>
<h2 id="testing-our-server">Testing Our Server</h2>
<p>Using command-line tools is the best way to see these features in action.</p>
<h3 id="testing-keep-alive">Testing Keep-Alive</h3>
<p>We can use <code>curl</code>&rsquo;s verbose mode (<code>-v</code>) to see connection reuse. We&rsquo;ll make two requests to our server on the same command line.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl --http1.1 -v http://127.0.0.1:9000/headers http://127.0.0.1:9000/status/204
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>*   Trying 127.0.0.1:9000...
</span></span><span style="display:flex;"><span>* Connected to 127.0.0.1 <span style="color:#81a1c1">(</span>127.0.0.1<span style="color:#81a1c1">)</span> port <span style="color:#b48ead">9000</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#0)</span>
</span></span><span style="display:flex;"><span>&gt; GET /headers HTTP/1.1
</span></span><span style="display:flex;"><span>&gt; Host: 127.0.0.1:9000
</span></span><span style="display:flex;"><span>&gt; ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt; HTTP/1.1 <span style="color:#b48ead">200</span> OK
</span></span><span style="display:flex;"><span>&lt; Connection: keep-alive
</span></span><span style="display:flex;"><span>&lt; ...
</span></span><span style="display:flex;"><span>* Connection <span style="color:#616e87;font-style:italic">#0 to host 127.0.0.1 left intact</span>
</span></span><span style="display:flex;"><span>* Found bundle <span style="color:#81a1c1;font-weight:bold">for</span> host 127.0.0.1: 0x1400084c0 <span style="color:#81a1c1">[</span>can pipeline<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>* Re-using existing connection! <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#0) with host 127.0.0.1</span>
</span></span><span style="display:flex;"><span>&gt; GET /status/204 HTTP/1.1
</span></span><span style="display:flex;"><span>&gt; Host: 127.0.0.1:9000
</span></span><span style="display:flex;"><span>&gt; ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt; HTTP/1.1 <span style="color:#b48ead">204</span> No Content
</span></span><span style="display:flex;"><span>&lt; Connection: keep-alive
</span></span><span style="display:flex;"><span>&lt; ...
</span></span><span style="display:flex;"><span>* Connection <span style="color:#616e87;font-style:italic">#0 to host 127.0.0.1 left intact</span>
</span></span></code></pre></div><p>The key lines are <code>Re-using existing connection!</code> and <code>Connection #0 to host 127.0.0.1 left intact</code>, which confirm that the second request was sent over the same connection as the first.</p>
<h3 id="testing-the-host-header">Testing the Host Header</h3>
<p>Using <code>netcat</code> (<code>nc</code>), we can manually craft an HTTP request. First, let&rsquo;s try one without a <code>Host</code> header.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ <span style="color:#81a1c1">printf</span> <span style="color:#a3be8c">&#34;GET / HTTP/1.1\r\n\r\n&#34;</span> <span style="color:#eceff4">|</span> nc localhost <span style="color:#b48ead">9000</span>
</span></span></code></pre></div><p>The command returns nothing, and on the server side, we see our error log:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-plaintext" data-lang="plaintext"><span style="display:flex;"><span>2026/01/24 19:34:39 ERROR http error: required &#39;Host&#39; header not found
</span></span></code></pre></div><p>Success! Now let&rsquo;s add the <code>Host</code> header.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ <span style="color:#81a1c1">printf</span> <span style="color:#a3be8c">&#34;GET /headers HTTP/1.1\r\nHost: localhost\r\n\r\n&#34;</span> <span style="color:#eceff4">|</span> nc localhost <span style="color:#b48ead">9000</span>
</span></span><span style="display:flex;"><span>HTTP/1.1 <span style="color:#b48ead">200</span> OK
</span></span><span style="display:flex;"><span>Connection: keep-alive
</span></span><span style="display:flex;"><span>Content-Type: application/json
</span></span><span style="display:flex;"><span>Transfer-Encoding: chunked
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#b48ead">61</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span><span style="color:#a3be8c">&#34;Accept-Encoding&#34;</span>:<span style="color:#81a1c1">[</span><span style="color:#a3be8c">&#34;gzip&#34;</span><span style="color:#81a1c1">]</span>,<span style="color:#a3be8c">&#34;Host&#34;</span>:<span style="color:#81a1c1">[</span><span style="color:#a3be8c">&#34;localhost&#34;</span><span style="color:#81a1c1">]</span>,<span style="color:#a3be8c">&#34;User-Agent&#34;</span>:<span style="color:#81a1c1">[</span><span style="color:#a3be8c">&#34;Go-http-client/1.1&#34;</span><span style="color:#81a1c1">]}</span>
</span></span><span style="display:flex;"><span><span style="color:#b48ead">0</span>
</span></span></code></pre></div><p>It works perfectly, and we even get a chunked response back!</p>
<h3 id="testing-chunked-encoding">Testing Chunked Encoding</h3>
<p>Our server has an <code>/echo/chunked</code> endpoint that streams the request body right back to the response. We can use <code>curl</code> to send a chunked request to it.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># We send two chunks: &#34;hello&#34; and &#34; world&#34;</span>
</span></span><span style="display:flex;"><span>$ <span style="color:#81a1c1">(</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">printf</span> <span style="color:#a3be8c">&#34;5\r\nhello\r\n&#34;</span><span style="color:#eceff4">;</span>
</span></span><span style="display:flex;"><span>  sleep 1<span style="color:#eceff4">;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">printf</span> <span style="color:#a3be8c">&#34;6\r\n world\r\n&#34;</span><span style="color:#eceff4">;</span>
</span></span><span style="display:flex;"><span>  sleep 1<span style="color:#eceff4">;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">printf</span> <span style="color:#a3be8c">&#34;0\r\n\r\n&#34;</span><span style="color:#eceff4">;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">)</span> <span style="color:#eceff4">|</span> curl --http1.1 -X POST --header <span style="color:#a3be8c">&#34;Transfer-Encoding: chunked&#34;</span> -T - http://127.0.0.1:9000/echo/chunked
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>hello world
</span></span></code></pre></div><p>The command pipes a manually created chunked body into <code>curl</code>. <code>curl</code> sends it to our server, which reads it using <code>chunkedBodyReader</code> and writes it back using our chunked <code>responseBodyWriter</code>. The final output <code>hello world</code> confirms the whole process worked.</p>
<h2 id="conclusion">Conclusion</h2>
<p>HTTP/1.1 was and still is an amazing protocol. It introduced connections re-use, virtual hosting, and streaming request and response bodies. The design choices made in HTTP/1.1 were so robust that they remain deeply embedded in the internet&rsquo;s infrastructure today.</p>
<p>If HTTP/1.1 was so great, why was HTTP/2 created? And what&rsquo;s the deal with HTTP/3? Stay tuned for the next post in this series where we start looking at <code>HTTP/2</code>.</p>
<p>See all of the code mentioned in this article here:
<details >
    <summary>
        go/main.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/http1.1-from-scratch/go/main.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;bufio&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;bytes&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;context&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;encoding/json&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;errors&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log/slog&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;math&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net/url&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;strconv&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;strings&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> nlcf <span style="color:#eceff4">=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#b48ead">0x0d</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0x0a</span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Server is a simple HTTP/1.1 server.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> Server <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Addr    <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span>	Handler http<span style="color:#eceff4">.</span>Handler
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ListenAndServe starts the server.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>s <span style="color:#81a1c1">*</span>Server<span style="color:#eceff4">)</span> <span style="color:#88c0d0">ListenAndServe</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	handler <span style="color:#81a1c1">:=</span> s<span style="color:#eceff4">.</span>Handler
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> handler <span style="color:#81a1c1">==</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		handler <span style="color:#eceff4">=</span> http<span style="color:#eceff4">.</span>DefaultServeMux
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	l<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> net<span style="color:#eceff4">.</span><span style="color:#88c0d0">Listen</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;tcp&#34;</span><span style="color:#eceff4">,</span> s<span style="color:#eceff4">.</span>Addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> l<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> l<span style="color:#eceff4">.</span><span style="color:#88c0d0">Accept</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">go</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> s<span style="color:#eceff4">.</span><span style="color:#88c0d0">handleConnection</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Error</span><span style="color:#eceff4">(</span>fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sprintf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;http error: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>s <span style="color:#81a1c1">*</span>Server<span style="color:#eceff4">)</span> <span style="color:#88c0d0">handleConnection</span><span style="color:#eceff4">(</span>conn net<span style="color:#eceff4">.</span>Conn<span style="color:#eceff4">)</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		shouldClose<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> s<span style="color:#eceff4">.</span><span style="color:#88c0d0">handleRequest</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">Is</span><span style="color:#eceff4">(</span>err<span style="color:#eceff4">,</span> io<span style="color:#eceff4">.</span>EOF<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> shouldClose <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>s <span style="color:#81a1c1">*</span>Server<span style="color:#eceff4">)</span> <span style="color:#88c0d0">handleRequest</span><span style="color:#eceff4">(</span>conn net<span style="color:#eceff4">.</span>Conn<span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">bool</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Limit headers to 1MB</span>
</span></span><span style="display:flex;"><span>	limitReader <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">LimitReader</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">,</span> <span style="color:#b48ead">1</span><span style="color:#81a1c1">*</span><span style="color:#b48ead">1024</span><span style="color:#81a1c1">*</span><span style="color:#b48ead">1024</span><span style="color:#eceff4">).(</span><span style="color:#81a1c1">*</span>io<span style="color:#eceff4">.</span>LimitedReader<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	reader <span style="color:#81a1c1">:=</span> bufio<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>limitReader<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	reqLineBytes<span style="color:#eceff4">,</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> reader<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadLine</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;read request line error: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	reqLine <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>reqLineBytes<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	req <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">new</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> found <span style="color:#81a1c1">bool</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>Method<span style="color:#eceff4">,</span> reqLine<span style="color:#eceff4">,</span> found <span style="color:#eceff4">=</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">Cut</span><span style="color:#eceff4">(</span>reqLine<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34; &#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>found <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid method&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span><span style="color:#88c0d0">methodValid</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">.</span>Method<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid method&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>RequestURI<span style="color:#eceff4">,</span> reqLine<span style="color:#eceff4">,</span> found <span style="color:#eceff4">=</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">Cut</span><span style="color:#eceff4">(</span>reqLine<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34; &#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>found <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid path&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> url<span style="color:#eceff4">.</span><span style="color:#88c0d0">ParseRequestURI</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">.</span>RequestURI<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid path: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>Proto <span style="color:#eceff4">=</span> reqLine
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>ProtoMajor<span style="color:#eceff4">,</span> req<span style="color:#eceff4">.</span>ProtoMinor<span style="color:#eceff4">,</span> found <span style="color:#eceff4">=</span> <span style="color:#88c0d0">parseProtocol</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">.</span>Proto<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>found <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid protocol&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>Header <span style="color:#eceff4">=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		line<span style="color:#eceff4">,</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> reader<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadLine</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#81a1c1">&amp;&amp;</span> err <span style="color:#81a1c1">!=</span> io<span style="color:#eceff4">.</span>EOF <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>line<span style="color:#eceff4">)</span> <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		k<span style="color:#eceff4">,</span> v<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> bytes<span style="color:#eceff4">.</span><span style="color:#88c0d0">Cut</span><span style="color:#eceff4">(</span>line<span style="color:#eceff4">,</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#39;:&#39;</span><span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid header&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		req<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span>strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">ToLower</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>k<span style="color:#eceff4">)),</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">TrimLeft</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>v<span style="color:#eceff4">),</span> <span style="color:#a3be8c">&#34; &#34;</span><span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> req<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">[</span><span style="color:#a3be8c">&#34;Host&#34;</span><span style="color:#eceff4">];</span> <span style="color:#eceff4">!</span>ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;required &#39;Host&#39; header not found&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">switch</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">ToLower</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span><span style="color:#88c0d0">Get</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Connection&#34;</span><span style="color:#eceff4">))</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">case</span> <span style="color:#a3be8c">&#34;keep-alive&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>		req<span style="color:#eceff4">.</span>Close <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">case</span> <span style="color:#a3be8c">&#34;close&#34;</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>		req<span style="color:#eceff4">.</span>Close <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	limitReader<span style="color:#eceff4">.</span>N <span style="color:#eceff4">=</span> math<span style="color:#eceff4">.</span>MaxInt64
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	ctx <span style="color:#81a1c1">:=</span> context<span style="color:#eceff4">.</span><span style="color:#88c0d0">Background</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	ctx <span style="color:#eceff4">=</span> context<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithValue</span><span style="color:#eceff4">(</span>ctx<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>LocalAddrContextKey<span style="color:#eceff4">,</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">LocalAddr</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>	ctx<span style="color:#eceff4">,</span> cancelCtx <span style="color:#81a1c1">:=</span> context<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithCancel</span><span style="color:#eceff4">(</span>ctx<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> <span style="color:#88c0d0">cancelCtx</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	contentLength<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">parseContentLength</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span><span style="color:#88c0d0">Get</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Content-Length&#34;</span><span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>ContentLength <span style="color:#eceff4">=</span> contentLength
</span></span><span style="display:flex;"><span>	isChunked <span style="color:#81a1c1">:=</span> req<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span><span style="color:#88c0d0">Get</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Transfer-Encoding&#34;</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;chunked&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> req<span style="color:#eceff4">.</span>ContentLength <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#81a1c1">&amp;&amp;</span> <span style="color:#eceff4">!</span>isChunked <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		req<span style="color:#eceff4">.</span>Body <span style="color:#eceff4">=</span> noBody<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> isChunked <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			req<span style="color:#eceff4">.</span>Body <span style="color:#eceff4">=</span> <span style="color:#81a1c1">&amp;</span>chunkedBodyReader<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				reader<span style="color:#eceff4">:</span> reader<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			req<span style="color:#eceff4">.</span>Body <span style="color:#eceff4">=</span> <span style="color:#81a1c1">&amp;</span>bodyReader<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				reader<span style="color:#eceff4">:</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">LimitReader</span><span style="color:#eceff4">(</span>reader<span style="color:#eceff4">,</span> req<span style="color:#eceff4">.</span>ContentLength<span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>RemoteAddr <span style="color:#eceff4">=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">RemoteAddr</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">String</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	w <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>responseBodyWriter<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		req<span style="color:#eceff4">:</span>     req<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">:</span>    conn<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		headers<span style="color:#eceff4">:</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	s<span style="color:#eceff4">.</span>Handler<span style="color:#eceff4">.</span><span style="color:#88c0d0">ServeHTTP</span><span style="color:#eceff4">(</span>w<span style="color:#eceff4">,</span> req<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithContext</span><span style="color:#eceff4">(</span>ctx<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> w<span style="color:#eceff4">.</span><span style="color:#88c0d0">flush</span><span style="color:#eceff4">();</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> req<span style="color:#eceff4">.</span>Close<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> noBody <span style="color:#81a1c1;font-weight:bold">struct</span><span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>noBody<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Read</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span> <span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> io<span style="color:#eceff4">.</span>EOF <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>noBody<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span>             <span style="color:#eceff4">{</span> <span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">parseContentLength</span><span style="color:#eceff4">(</span>headerval <span style="color:#81a1c1">string</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int64</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> headerval <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> strconv<span style="color:#eceff4">.</span><span style="color:#88c0d0">ParseInt</span><span style="color:#eceff4">(</span>headerval<span style="color:#eceff4">,</span> <span style="color:#b48ead">10</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">64</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">parseProtocol</span><span style="color:#eceff4">(</span>proto <span style="color:#81a1c1">string</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">bool</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">switch</span> proto <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">case</span> <span style="color:#a3be8c">&#34;HTTP/1.0&#34;</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">case</span> <span style="color:#a3be8c">&#34;HTTP/1.1&#34;</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">methodValid</span><span style="color:#eceff4">(</span>method <span style="color:#81a1c1">string</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">bool</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">switch</span> method <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">case</span> http<span style="color:#eceff4">.</span>MethodGet<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodHead<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodPost<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodPut<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodPatch<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodDelete<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodConnect<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodOptions<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodTrace<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> bodyReader <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	reader io<span style="color:#eceff4">.</span>Reader
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>bodyReader<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Read</span><span style="color:#eceff4">(</span>p <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>n <span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> err <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> r<span style="color:#eceff4">.</span>reader<span style="color:#eceff4">.</span><span style="color:#88c0d0">Read</span><span style="color:#eceff4">(</span>p<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>bodyReader<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">Copy</span><span style="color:#eceff4">(</span>io<span style="color:#eceff4">.</span>Discard<span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>reader<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> chunkedBodyReader <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	reader <span style="color:#81a1c1">*</span>bufio<span style="color:#eceff4">.</span>Reader
</span></span><span style="display:flex;"><span>	n      <span style="color:#81a1c1">int64</span> <span style="color:#616e87;font-style:italic">// bytes left in current chunk</span>
</span></span><span style="display:flex;"><span>	err    <span style="color:#81a1c1">error</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>chunkedBodyReader<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Read</span><span style="color:#eceff4">(</span>p <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>n <span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> err <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>n <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		r<span style="color:#eceff4">.</span>n<span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>err <span style="color:#eceff4">=</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">readChunkSize</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>n <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> io<span style="color:#eceff4">.</span>EOF
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#81a1c1">int64</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>p<span style="color:#eceff4">))</span> <span style="color:#eceff4">&gt;</span> r<span style="color:#eceff4">.</span>n <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		p <span style="color:#eceff4">=</span> p<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">:</span>r<span style="color:#eceff4">.</span>n<span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	n<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> r<span style="color:#eceff4">.</span>reader<span style="color:#eceff4">.</span><span style="color:#88c0d0">Read</span><span style="color:#eceff4">(</span>p<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	r<span style="color:#eceff4">.</span>n <span style="color:#81a1c1">-=</span> <span style="color:#81a1c1">int64</span><span style="color:#eceff4">(</span>n<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>n <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#81a1c1">&amp;&amp;</span> err <span style="color:#81a1c1">==</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Read trailing \r\n</span>
</span></span><span style="display:flex;"><span>		b<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>reader<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadByte</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			r<span style="color:#eceff4">.</span>err <span style="color:#eceff4">=</span> err
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> n<span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> b <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#39;\r&#39;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			r<span style="color:#eceff4">.</span>err <span style="color:#eceff4">=</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;missing \r after chunk&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> n<span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		b<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> r<span style="color:#eceff4">.</span>reader<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadByte</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			r<span style="color:#eceff4">.</span>err <span style="color:#eceff4">=</span> err
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> n<span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> b <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#39;\n&#39;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			r<span style="color:#eceff4">.</span>err <span style="color:#eceff4">=</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;missing \n after chunk&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> n<span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	r<span style="color:#eceff4">.</span>err <span style="color:#eceff4">=</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> n<span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>chunkedBodyReader<span style="color:#eceff4">)</span> <span style="color:#88c0d0">readChunkSize</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int64</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	line<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">readLine</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// chunkSize is hex</span>
</span></span><span style="display:flex;"><span>	n<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> strconv<span style="color:#eceff4">.</span><span style="color:#88c0d0">ParseInt</span><span style="color:#eceff4">(</span>strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">TrimSpace</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>line<span style="color:#eceff4">)),</span> <span style="color:#b48ead">16</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">64</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> n <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Read trailers</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			line<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span><span style="color:#88c0d0">readLine</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>line<span style="color:#eceff4">)</span> <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> n<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>chunkedBodyReader<span style="color:#eceff4">)</span> <span style="color:#88c0d0">readLine</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> line <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		b<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>reader<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadByte</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> b <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#39;\n&#39;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		line <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>line<span style="color:#eceff4">,</span> b<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">TrimRight</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>line<span style="color:#eceff4">),</span> <span style="color:#a3be8c">&#34;\r&#34;</span><span style="color:#eceff4">),</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>chunkedBodyReader<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">Copy</span><span style="color:#eceff4">(</span>io<span style="color:#eceff4">.</span>Discard<span style="color:#eceff4">,</span> r<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> responseBodyWriter <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	req             <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request
</span></span><span style="display:flex;"><span>	conn            net<span style="color:#eceff4">.</span>Conn
</span></span><span style="display:flex;"><span>	sentHeaders     <span style="color:#81a1c1">bool</span>
</span></span><span style="display:flex;"><span>	headers         http<span style="color:#eceff4">.</span>Header
</span></span><span style="display:flex;"><span>	chunkedEncoding <span style="color:#81a1c1">bool</span>
</span></span><span style="display:flex;"><span>	bodyBuffer      <span style="color:#81a1c1">*</span>bytes<span style="color:#eceff4">.</span>Buffer
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Header</span><span style="color:#eceff4">()</span> http<span style="color:#eceff4">.</span>Header <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> r<span style="color:#eceff4">.</span>headers
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>b <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>r<span style="color:#eceff4">.</span>sentHeaders <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">.</span><span style="color:#88c0d0">Get</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Content-Type&#34;</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			r<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">.</span><span style="color:#88c0d0">Set</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Content-Type&#34;</span><span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">DetectContentType</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		r<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteHeader</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>StatusOK<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>chunkedEncoding <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		chunkSize <span style="color:#81a1c1">:=</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sprintf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;%x\r\n&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>chunkSize<span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	n<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> n<span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>chunkedEncoding <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>nlcf<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> n<span style="color:#eceff4">,</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> n<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Flush</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>r<span style="color:#eceff4">.</span>sentHeaders <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		r<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteHeader</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>StatusOK<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> flusher<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.(</span><span style="color:#81a1c1;font-weight:bold">interface</span><span style="color:#eceff4">{</span> <span style="color:#88c0d0">Flush</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">});</span> ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		flusher<span style="color:#eceff4">.</span><span style="color:#88c0d0">Flush</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">flush</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>chunkedEncoding <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;0\r\n\r\n&#34;</span><span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	r<span style="color:#eceff4">.</span><span style="color:#88c0d0">writeBufferedBody</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">WriteHeader</span><span style="color:#eceff4">(</span>statusCode <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>sentHeaders <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Warn</span><span style="color:#eceff4">(</span>fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sprintf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;WriteHeader called twice, second time with: %d&#34;</span><span style="color:#eceff4">,</span> statusCode<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	r<span style="color:#eceff4">.</span><span style="color:#88c0d0">writeHeader</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>req<span style="color:#eceff4">.</span>Proto<span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">,</span> statusCode<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	r<span style="color:#eceff4">.</span>sentHeaders <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>	r<span style="color:#eceff4">.</span><span style="color:#88c0d0">writeBufferedBody</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">writeBufferedBody</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>bodyBuffer <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		_<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">.</span>bodyBuffer<span style="color:#eceff4">.</span><span style="color:#88c0d0">Bytes</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Error</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error writing buffered body&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;err&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		r<span style="color:#eceff4">.</span>bodyBuffer <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">writeHeader</span><span style="color:#eceff4">(</span>conn io<span style="color:#eceff4">.</span>Writer<span style="color:#eceff4">,</span> proto <span style="color:#81a1c1">string</span><span style="color:#eceff4">,</span> headers http<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">,</span> statusCode <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> clSet <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">[</span><span style="color:#a3be8c">&#34;Content-Length&#34;</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> teSet <span style="color:#81a1c1">:=</span> r<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">[</span><span style="color:#a3be8c">&#34;Transfer-Encoding&#34;</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>clSet <span style="color:#81a1c1">&amp;&amp;</span> <span style="color:#eceff4">!</span>teSet <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		r<span style="color:#eceff4">.</span>chunkedEncoding <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>		r<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">.</span><span style="color:#88c0d0">Set</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Transfer-Encoding&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;chunked&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>req<span style="color:#eceff4">.</span>Close <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		r<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">.</span><span style="color:#88c0d0">Set</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Connection&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;close&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		r<span style="color:#eceff4">.</span>headers<span style="color:#eceff4">.</span><span style="color:#88c0d0">Set</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Connection&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;keep-alive&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">,</span> proto<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#39; &#39;</span><span style="color:#eceff4">});</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">,</span> strconv<span style="color:#eceff4">.</span><span style="color:#88c0d0">FormatInt</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">int64</span><span style="color:#eceff4">(</span>statusCode<span style="color:#eceff4">),</span> <span style="color:#b48ead">10</span><span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#39; &#39;</span><span style="color:#eceff4">});</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">StatusText</span><span style="color:#eceff4">(</span>statusCode<span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>nlcf<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> k<span style="color:#eceff4">,</span> vals <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> headers <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">for</span> _<span style="color:#eceff4">,</span> val <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> vals <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">,</span> k<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#39;:&#39;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#39; &#39;</span><span style="color:#eceff4">});</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">,</span> val<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>nlcf<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>nlcf<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	addr <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;127.0.0.1:9000&#34;</span>
</span></span><span style="display:flex;"><span>	mux <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServeMux</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">Handle</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;/&#34;</span><span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">FileServer</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span><span style="color:#88c0d0">Dir</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;.&#34;</span><span style="color:#eceff4">)))</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandleFunc</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;/echo&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">defer</span> r<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		b<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadAll</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			w<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteHeader</span><span style="color:#eceff4">(</span><span style="color:#b48ead">400</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		w<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandleFunc</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;/echo/chunked&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">defer</span> r<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		io<span style="color:#eceff4">.</span><span style="color:#88c0d0">Copy</span><span style="color:#eceff4">(</span>w<span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandleFunc</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;/status/{status}&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		status<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> strconv<span style="color:#eceff4">.</span><span style="color:#88c0d0">ParseInt</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">.</span><span style="color:#88c0d0">PathValue</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;status&#34;</span><span style="color:#eceff4">),</span> <span style="color:#b48ead">10</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">64</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			w<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteHeader</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>StatusBadRequest<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			io<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>w<span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sprintf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;error: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		w<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteHeader</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">(</span>status<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandleFunc</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;/headers&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		w<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;content-type&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;application/json&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		json<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewEncoder</span><span style="color:#eceff4">(</span>w<span style="color:#eceff4">).</span><span style="color:#88c0d0">Encode</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandleFunc</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;/nothing&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{})</span>
</span></span><span style="display:flex;"><span>	s <span style="color:#81a1c1">:=</span> Server<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Addr<span style="color:#eceff4">:</span>    addr<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		Handler<span style="color:#eceff4">:</span> mux<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Starting web server: http://%s&#34;</span><span style="color:#eceff4">,</span> addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> s<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServe</span><span style="color:#eceff4">();</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatal</span><span style="color:#eceff4">(</span>err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
</p>
]]></content:encoded></item><item><title>WHOIS is dead, long live RDAP</title><link>https://kmcd.dev/posts/whois-from-scratch/</link><pubDate>Tue, 06 Jan 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/whois-from-scratch/</guid><description><![CDATA[ 
                <p> <img hspace="5" src="https://kmcd.dev/posts/whois-from-scratch/cover.svg" /> </p>
                
                WHOIS is dead. To memorialize this piece of internet history, let&#39;s build a tiny implementation from scratch.
                ]]></description><content:encoded><![CDATA[<p>The <code>whois</code> protocol is dead. For decades, it was a fundamental tool for network reconnaissance, but its time has passed. The protocol was officially sunset for all generic top-level domains in early 2025, replaced by the more modern, web-based protocol, <a href="https://about.rdap.org/" rel="external">RDAP</a>.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/whois-from-scratch/whois-dead_hu_e419a49d43036ce6.webp" class="center" width="500px"/>
    


<p>So why talk about WHOIS now? To pay our respects. Because the WHOIS protocol is so simple, it makes a perfect case study for basic network programming and a window into an earlier era of the internet. To help memorialize this piece of internet history, we will build a tiny implementation from scratch and understand why its death was necessary in the process.</p>
<h4 id="what-is-whois">What is WHOIS?</h4>
<p>Think of WHOIS as the internet&rsquo;s public directory. Its primary job is to resolve a domain name into a set of administrative details.</p>
<p>When you query <code>example.com</code>, you aren&rsquo;t asking for the website content; you are asking for the paper trail. A standard response returns the <strong>Registrar</strong> (the vendor, such as Namecheap or GoDaddy), the <strong>Name Servers</strong> (which direct traffic), and key dates regarding the domain&rsquo;s creation and expiration. In the early days of the web, this output also listed the owner&rsquo;s full name, address, and phone number. Today, privacy regulations like GDPR have largely forced that personal information behind generic &ldquo;Redacted for Privacy&rdquo; placeholders.</p>
<h3 id="who-provides-this-data">Who provides this data?</h3>
<p>WHOIS data is a requirement enforced by ICANN (Internet Corporation for Assigned Names and Numbers). ICANN sets the rules, Registries (like Verisign for .com) manage the master lists for their TLDs, and Registrars (like Google Domains or Namecheap) sell the names to you. Domain registrars are contractually obligated to maintain this registration data and make it available to the public.</p>
<h3 id="why-do-we-need-it">Why do we need it?</h3>
<p>While often used by developers to check if a cool side project name is taken, WHOIS is critical infrastructure for maintaining the internet&rsquo;s health.</p>
<p>Despite these redactions, the protocol remains vital for security. ICANN mandates that every domain record must publicly display an abuse contact email and phone number. This provides a direct line for network operators to report domains hosting malware, phishing schemes, or spam.</p>
<p>Security researchers have also pivoted their tactics. instead of looking for a specific person, they look for digital fingerprints. If a cluster of 500 suspicious domains appears on the network, registered simultaneously via the same obscure Name Server and Registrar, it strongly suggests a coordinated botnet. You don&rsquo;t need to know the name of the attacker to know the assets are connected.</p>
<p>Investigative journalists use historical WHOIS data to map state-sponsored disinformation campaigns. For modern investigations, RDAP introduces &ldquo;tiered access,&rdquo; theoretically allowing vetted professionals to request unredacted data for legitimate purposes, though this process is still maturing. Also, a new initiative called <a href="https://www.icann.org/rdrs-en" rel="external">RDRS</a> aims to standardize access to nonpublic registration data for legitimate purposes.</p>
<h3 id="how-whois-works">How WHOIS Works</h3>
<p>The WHOIS protocol, defined in <a href="https://www.rfc-editor.org/rfc/rfc3912" rel="external">RFC 3912</a>, is a simple exchange over a TCP connection.</p>
<ol>
<li><strong>CONNECT</strong>: The client opens a TCP socket to a WHOIS server (on port 43).</li>
<li><strong>ASK</strong>: The client sends the query: a single line of text like <code>example.com</code>, terminated by a carriage return and line feed (<code>&lt;CR&gt;&lt;LF&gt;</code>).</li>
<li><strong>RESPONSE</strong>: The server streams back the registration data as plain text.</li>
<li><strong>DISCONNECT</strong>: The server kills the connection.</li>
</ol>
<p>There are no headers, no authentication, and no complex data formats (more on that later). It is quite literally one of the simplest protocols imaginable. This simplicity makes it a good candidate for a small project to demonstrate basic networking concepts.</p>
<h3 id="building-a-whois-server">Building a WHOIS Server</h3>
<p>Let&rsquo;s turn theory into code. Because the protocol is so trivial, we can implement a functional server in Go using a few lines of code and the Go standard library. We can then verify that it works using the tools already installed on your machine, like telnet or the whois command itself.</p>
<h4 id="whois-server-implementation">WHOIS Server Implementation</h4>
<p>We&rsquo;ll add a <code>records</code> map to hold fake domain data and implement a <code>handleConnection</code> function to process queries and send back the corresponding record.</p>
<details >
    <summary>
        whois-server/main.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/whois-is-dead/go/whois-server/main.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;bufio&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;strings&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// whoisData now holds a single, static WHOIS record for debugging purposes.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> whoisData <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">map</span><span style="color:#eceff4">[</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">]</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;google.com&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">`Domain Name: google.com
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">Registrar: My Go Server
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">Creation Date: 2025-12-15T00:00:00Z
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">`</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;example.com&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">`Domain Name: example.com
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">Registrar: My Go Server
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">Creation Date: 2025-12-15T00:00:00Z
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">`</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">handleConnection</span><span style="color:#eceff4">(</span>conn net<span style="color:#eceff4">.</span>Conn<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;new connection from %s&#34;</span><span style="color:#eceff4">,</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">RemoteAddr</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;connection to %s closed&#34;</span><span style="color:#eceff4">,</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">RemoteAddr</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	scanner <span style="color:#81a1c1">:=</span> bufio<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewScanner</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> clientQuery <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Read the first line from the client.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> scanner<span style="color:#eceff4">.</span><span style="color:#88c0d0">Scan</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		clientQuery <span style="color:#eceff4">=</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">TrimSpace</span><span style="color:#eceff4">(</span>scanner<span style="color:#eceff4">.</span><span style="color:#88c0d0">Text</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> scanner<span style="color:#eceff4">.</span><span style="color:#88c0d0">Err</span><span style="color:#eceff4">();</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error reading from client: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> clientQuery <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Client disconnected without sending a query or sent an empty query.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Send a minimal response in case the client expects *something*</span>
</span></span><span style="display:flex;"><span>		_<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fprint</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;No query provided.\r\n&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error writing empty query response: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Received query: %q&#34;</span><span style="color:#eceff4">,</span> clientQuery<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> response <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> data<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> whoisData<span style="color:#eceff4">[</span>strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">ToLower</span><span style="color:#eceff4">(</span>clientQuery<span style="color:#eceff4">)];</span> ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		response <span style="color:#eceff4">=</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReplaceAll</span><span style="color:#eceff4">(</span>data<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;\n&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;\r\n&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		response <span style="color:#eceff4">=</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sprintf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;No match for %s\r\n&#34;</span><span style="color:#eceff4">,</span> clientQuery<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Write the response and close the connection.</span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fprint</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">,</span> response<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error writing response to client: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	addr <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;:43&#34;</span>
</span></span><span style="display:flex;"><span>	listener<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> net<span style="color:#eceff4">.</span><span style="color:#88c0d0">Listen</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;tcp&#34;</span><span style="color:#eceff4">,</span> addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error listening: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> listener<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Static WHOIS server listening on %s&#34;</span><span style="color:#eceff4">,</span> addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> listener<span style="color:#eceff4">.</span><span style="color:#88c0d0">Accept</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error accepting connection: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">go</span> <span style="color:#88c0d0">handleConnection</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<p>To run the server, execute:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>go run ./whois-server
</span></span></code></pre></div><p>With the server running, you can now test it with <code>telnet</code> and <code>whois</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>telnet localhost <span style="color:#b48ead">43</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Trying ::1...</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Connected to localhost.</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Escape character is &#39;^]&#39;.</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># example.com</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Domain Name: example.com</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Registrar: My Go Server</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Creation Date: 2025-12-15T00:00:00Z</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Connection closed by foreign host.</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>whois -h localhost google.com
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Domain Name: google.com</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Registrar: My Go Server</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Creation Date: 2025-12-15T00:00:00Z</span>
</span></span></code></pre></div><h3 id="real-world-complications">Real-World Complications</h3>
<p>Our server works for the domains stored in its local <code>records</code> map, but the real <code>WHOIS</code> system is a distributed, federated system of registries and registrars, not a single database.</p>
<p>This leads to concepts like <strong>&ldquo;thin&rdquo; and &ldquo;thick&rdquo; lookups</strong>. A &ldquo;thick&rdquo; registry (like <code>.org</code>) holds all the data, and one query is enough. A &ldquo;thin&rdquo; registry (like <code>.com</code>) only knows which registrar manages a domain (e.g., GoDaddy, Namecheap). A <code>whois</code> client querying a &ldquo;thin&rdquo; registry gets a referral and must make a second query to the correct registrar&rsquo;s WHOIS server to get the full details.</p>
<p>This system is brittle, relying on parsing unstructured text to find the referral server. Classic <code>whois</code> clients, such as the <a href="https://github.com/rfc1036/whois/blob/next/whois.c" rel="external"><code>rfc1036/whois</code></a>, handle this by scanning each line of text for known referral markers using functions like <code>find_referral_server_iana</code>. This approach works, but it is fragile because every registry formats output differently. The brittleness of parsing free-form text was a key driver to replace it with a modern protocol that uses structured data like JSON.</p>
<h3 id="rdap-the-modern-successor">RDAP: The Modern Successor</h3>
<p>The push to replace it began back in 2013, when an ICANN Expert Working Group recommended that the WHOIS protocol should be tossed out. They proposed a system that would keep information secret from most users, disclosing data only for specific &ldquo;permissible purposes&rdquo; like legal actions or trademark enforcement. Notably, <strong>journalism was excluded</strong> from this list, despite WHOIS historically being a key tool for investigative reporting.</p>
<p>After years of debate and voting, the transition became official. On January 28, 2025, WHOIS was officially sunset for generic Top-Level Domains (gTLDs). Registries are no longer required to support it, and the industry has shifted its focus to RDAP (Registration Data Access Protocol).</p>
<p>RDAP performs the same function as WHOIS but it uses HTTPS and returns JSON instead of plain text.</p>
<table>
  <thead>
      <tr>
          <th>Feature</th>
          <th>WHOIS</th>
          <th>RDAP</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Transport</td>
          <td>TCP Port 43</td>
          <td>HTTP/HTTPS (Port 80/443)</td>
      </tr>
      <tr>
          <td>Format</td>
          <td>Unstructured Plain Text</td>
          <td>Structured JSON (machine-readable)</td>
      </tr>
      <tr>
          <td>Security</td>
          <td>None</td>
          <td>Standard Web Security (TLS, Auth)</td>
      </tr>
      <tr>
          <td>Discovery</td>
          <td>Brittle, text-based referrals</td>
          <td>Standardized discovery.</td>
      </tr>
  </tbody>
</table>
<p>You can try it with <code>curl</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -L https://rdap.verisign.com/com/v1/domain/google.com
</span></span></code></pre></div><details >
    <summary>
        Output (click to expand)</summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;objectClassName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;domain&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;handle&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;2138514_DOMAIN_COM-VRSN&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;ldhName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;GOOGLE.COM&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;links&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;value&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://rdap.verisign.com/com/v1/domain/GOOGLE.COM&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;rel&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;self&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;href&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://rdap.verisign.com/com/v1/domain/GOOGLE.COM&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;type&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;application/rdap+json&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;value&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://rdap.markmonitor.com/rdap/domain/GOOGLE.COM&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;rel&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;related&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;href&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://rdap.markmonitor.com/rdap/domain/GOOGLE.COM&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;type&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;application/rdap+json&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;status&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;client delete prohibited&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;client transfer prohibited&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;client update prohibited&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;server delete prohibited&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;server transfer prohibited&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;server update prohibited&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;entities&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;objectClassName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;entity&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;handle&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;292&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;roles&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#34;registrar&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;links&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;href&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;http://www.markmonitor.com&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;type&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;text/html&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;value&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://rdap.markmonitor.com/rdap/&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;rel&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;about&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>      <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;publicIds&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;type&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;IANA Registrar ID&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;identifier&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;292&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>      <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;vcardArray&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#34;vcard&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>          <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a3be8c">&#34;version&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#eceff4">{},</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a3be8c">&#34;text&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a3be8c">&#34;4.0&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>          <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a3be8c">&#34;fn&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#eceff4">{},</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a3be8c">&#34;text&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a3be8c">&#34;MarkMonitor Inc.&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>      <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;entities&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;objectClassName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;entity&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;roles&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a3be8c">&#34;abuse&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;vcardArray&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a3be8c">&#34;vcard&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>              <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a3be8c">&#34;version&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#eceff4">{},</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a3be8c">&#34;text&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a3be8c">&#34;4.0&#34;</span>
</span></span><span style="display:flex;"><span>              <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>              <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a3be8c">&#34;fn&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#eceff4">{},</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a3be8c">&#34;text&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a3be8c">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>              <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>              <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a3be8c">&#34;tel&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#81a1c1">&#34;type&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;voice&#34;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a3be8c">&#34;uri&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a3be8c">&#34;tel:+1.2086851750&#34;</span>
</span></span><span style="display:flex;"><span>              <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>              <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a3be8c">&#34;email&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#eceff4">{},</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a3be8c">&#34;text&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a3be8c">&#34;abusecomplaints@markmonitor.com&#34;</span>
</span></span><span style="display:flex;"><span>              <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>            <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>          <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>      <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;events&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;eventAction&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;registration&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;eventDate&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;1997-09-15T04:00:00Z&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;eventAction&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;expiration&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;eventDate&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;2028-09-14T04:00:00Z&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;eventAction&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;last changed&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;eventDate&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;2019-09-09T15:39:04Z&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;eventAction&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;last update of RDAP database&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;eventDate&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;2025-12-16T20:15:07Z&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;secureDNS&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;delegationSigned&#34;</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;nameservers&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;objectClassName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;nameserver&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;ldhName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;NS1.GOOGLE.COM&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;objectClassName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;nameserver&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;ldhName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;NS2.GOOGLE.COM&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;objectClassName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;nameserver&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;ldhName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;NS3.GOOGLE.COM&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;objectClassName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;nameserver&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;ldhName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;NS4.GOOGLE.COM&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;rdapConformance&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;rdap_level_0&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;icann_rdap_technical_implementation_guide_1&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;icann_rdap_response_profile_1&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;notices&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;title&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Terms of Service&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;description&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#34;Service subject to Terms of Use.&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;links&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;href&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://www.verisign.com/domain-names/registration-data-access-protocol/terms-service/index.xhtml&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;type&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;text/html&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;value&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://rdap.verisign.com/com/v1/domain/google.com&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;rel&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;terms-of-service&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>      <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;title&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Status Codes&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;description&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#34;For more information on domain status codes, please visit https://icann.org/epp&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;links&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;href&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://icann.org/epp&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;type&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;text/html&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>      <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;title&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;RDDS Inaccuracy Complaint Form&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;description&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#34;URL of the ICANN RDDS Inaccuracy Complaint Form: https://icann.org/wicf&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;links&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;href&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://icann.org/wicf&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;type&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;text/html&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;value&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://rdap.verisign.com/com/v1/domain/google.com&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">&#34;rel&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;help&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>      <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<p>The response is a structured JSON object that is far easier to parse than the free-form text of WHOIS.</p>
<h3 id="why-dev-domains-dont-work">Why <code>.dev</code> domains don&rsquo;t work</h3>
<p>If you try to run a legacy WHOIS lookup against a modern TLD like <code>.dev</code>, you will likely hit a dead end. Google, along with many newer registries, has effectively deprecated port 43. They are not required to support the old text-based protocol, so they don&rsquo;t.</p>
<p>Instead, querying a <code>.dev</code> domain via the command line often returns a generic placeholder from IANA. It tells you who manages the <code>.dev</code> registry, but it won&rsquo;t tell you anything about the specific domain you asked for (like <code>kmcd.dev</code>).</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ whois kmcd.dev
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># % IANA WHOIS server</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># % for more information on IANA, visit http://www.iana.org</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># % This query returned 1 object</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># domain:       DEV</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># organisation: Charleston Road Registry Inc.</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># ...</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># remarks:      Registration information: https://www.registry.google</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># source:       IANA</span>
</span></span></code></pre></div><p>To get the actual data, you are supposed to use <code>rdap</code>. As shown below, the <code>rdap</code> command retrieves the full registration details you would expect:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>rdap kmcd.dev
</span></span></code></pre></div><details >
    <summary>
        Output (click to expand)</summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Domain:
</span></span><span style="display:flex;"><span>  Domain Name: kmcd.dev
</span></span><span style="display:flex;"><span>  Handle: E04E36511-DEV
</span></span><span style="display:flex;"><span>  Status: client transfer prohibited
</span></span><span style="display:flex;"><span>  Conformance: rdap_level_0
</span></span><span style="display:flex;"><span>  Conformance: icann_rdap_response_profile_1
</span></span><span style="display:flex;"><span>  Conformance: icann_rdap_technical_implementation_guide_1
</span></span><span style="display:flex;"><span>  Notice:
</span></span><span style="display:flex;"><span>    Title: RDAP Terms of Service
</span></span><span style="display:flex;"><span>    Description: By querying our Domain Database as part of the RDAP pilot program (RDAP Domain Database), you are agreeing to comply with these terms and acknowledging that your information will be used in accordance with Charleston Road Registry&#39;s Privacy Policy (https://www.registry.google/about/privacy.html), so please read the terms and Privacy Policy carefully.
</span></span><span style="display:flex;"><span>    Description: Any information provided is &#39;as is&#39; without any guarantee of accuracy.
</span></span><span style="display:flex;"><span>    Description: Please do not misuse the RDAP Domain Database. It is intended solely for query-based access on an experimental basis and should not be used for or relied upon for any other purpose.
</span></span><span style="display:flex;"><span>    Description: Don&#39;t use the RDAP Domain Database to allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations.
</span></span><span style="display:flex;"><span>    Description: Don&#39;t access our RDAP Domain Database through the use of high volume, automated electronic processes that send queries or data to the systems of Charleston Road Registry or any ICANN-accredited registrar.
</span></span><span style="display:flex;"><span>    Description: You may only use the information contained in the RDAP Domain Database for lawful purposes.
</span></span><span style="display:flex;"><span>    Description: Do not compile, repackage, disseminate, or otherwise use the information contained in the RDAP Domain Database in its entirety, or in any substantial portion, without our prior written permission.
</span></span><span style="display:flex;"><span>    Description: We may retain certain details about queries to our RDAP Domain Database for the purposes of detecting and preventing misuse.
</span></span><span style="display:flex;"><span>    Description: We reserve the right to restrict or deny your access to the RDAP Domain Database if we suspect that you have failed to comply with these terms.
</span></span><span style="display:flex;"><span>    Description: We reserve the right to modify or discontinue our participation in the RDAP pilot program and suspend or terminate access to the RDAP Domain Database at any time and for any reason in our sole discretion.
</span></span><span style="display:flex;"><span>    Description: Reminder that underlying Registrant data may be requested via ICANN&#39;s RDRS service (https://rdrs.icann.org/).
</span></span><span style="display:flex;"><span>    Description: We reserve the right to modify this agreement at any time.
</span></span><span style="display:flex;"><span>    Link: https://pubapi.registry.google/rdap/help/tos
</span></span><span style="display:flex;"><span>    Link: https://www.registry.google/policies/rdap-terms/
</span></span><span style="display:flex;"><span>  Notice:
</span></span><span style="display:flex;"><span>    Title: Status Codes
</span></span><span style="display:flex;"><span>    Description: For more information on domain status codes, please visit https://icann.org/epp
</span></span><span style="display:flex;"><span>    Link: https://icann.org/epp
</span></span><span style="display:flex;"><span>  Notice:
</span></span><span style="display:flex;"><span>    Title: RDDS Inaccuracy Complaint Form
</span></span><span style="display:flex;"><span>    Description: URL of the ICANN RDDS Inaccuracy Complaint Form: https://icann.org/wicf
</span></span><span style="display:flex;"><span>    Link: https://icann.org/wicf
</span></span><span style="display:flex;"><span>  Link: https://pubapi.registry.google/rdap/domain/kmcd.dev
</span></span><span style="display:flex;"><span>  Link: https://rdap.cloudflare.com/rdap/v1/domain/kmcd.dev
</span></span><span style="display:flex;"><span>  Event:
</span></span><span style="display:flex;"><span>    Action: registration
</span></span><span style="display:flex;"><span>    Actor: cloudflare
</span></span><span style="display:flex;"><span>    Date: 2024-05-18T19:33:01.182Z
</span></span><span style="display:flex;"><span>  Event:
</span></span><span style="display:flex;"><span>    Action: expiration
</span></span><span style="display:flex;"><span>    Date: 2034-05-18T19:33:01.182Z
</span></span><span style="display:flex;"><span>  Event:
</span></span><span style="display:flex;"><span>    Action: last update of RDAP database
</span></span><span style="display:flex;"><span>    Date: 2025-12-16T22:15:17.386Z
</span></span><span style="display:flex;"><span>  Event:
</span></span><span style="display:flex;"><span>    Action: last changed
</span></span><span style="display:flex;"><span>    Date: 2025-10-08T17:07:04.569Z
</span></span><span style="display:flex;"><span>  Secure DNS:
</span></span><span style="display:flex;"><span>    Zone Signed: true
</span></span><span style="display:flex;"><span>    Delegation Signed: true
</span></span><span style="display:flex;"><span>    DSData:
</span></span><span style="display:flex;"><span>      Key Tag: 2371
</span></span><span style="display:flex;"><span>      Algorithm: 13
</span></span><span style="display:flex;"><span>      Digest: 321F88BA26AB76AE885C06671798645ADF4D06448F52D67D627641FA28392AF7
</span></span><span style="display:flex;"><span>      DigestType: 2
</span></span><span style="display:flex;"><span>  Entity:
</span></span><span style="display:flex;"><span>    Handle: 1910
</span></span><span style="display:flex;"><span>    Public ID:
</span></span><span style="display:flex;"><span>      Type: IANA Registrar ID
</span></span><span style="display:flex;"><span>      Identifier: 1910
</span></span><span style="display:flex;"><span>    Remark:
</span></span><span style="display:flex;"><span>      Title: Incomplete Data
</span></span><span style="display:flex;"><span>      Type: object truncated due to unexplainable reasons
</span></span><span style="display:flex;"><span>      Description: Summary data only. For complete data, send a specific query for the object.
</span></span><span style="display:flex;"><span>    Link: https://pubapi.registry.google/rdap/entity/1910
</span></span><span style="display:flex;"><span>    Link: None
</span></span><span style="display:flex;"><span>    Role: registrar
</span></span><span style="display:flex;"><span>    vCard version: 4.0
</span></span><span style="display:flex;"><span>    vCard fn: CloudFlare, Inc.
</span></span><span style="display:flex;"><span>    Entity:
</span></span><span style="display:flex;"><span>      Status: active
</span></span><span style="display:flex;"><span>      Role: abuse
</span></span><span style="display:flex;"><span>      vCard version: 4.0
</span></span><span style="display:flex;"><span>      vCard fn: Abuse Team
</span></span><span style="display:flex;"><span>      vCard tel: tel:+1.4153197517
</span></span><span style="display:flex;"><span>      vCard email: registrar-abuse@cloudflare.com
</span></span><span style="display:flex;"><span>  Nameserver:
</span></span><span style="display:flex;"><span>    Nameserver: chuck.ns.cloudflare.com
</span></span><span style="display:flex;"><span>    Handle: 13F5B5F1_HOW-GOOGLE
</span></span><span style="display:flex;"><span>    Remark:
</span></span><span style="display:flex;"><span>      Title: Incomplete Data
</span></span><span style="display:flex;"><span>      Type: object truncated due to unexplainable reasons
</span></span><span style="display:flex;"><span>      Description: Summary data only. For complete data, send a specific query for the object.
</span></span><span style="display:flex;"><span>    Link: https://pubapi.registry.google/rdap/nameserver/chuck.ns.cloudflare.com
</span></span><span style="display:flex;"><span>  Nameserver:
</span></span><span style="display:flex;"><span>    Nameserver: oaklyn.ns.cloudflare.com
</span></span><span style="display:flex;"><span>    Handle: 40ADE79A0-GOOGLE
</span></span><span style="display:flex;"><span>    Remark:
</span></span><span style="display:flex;"><span>      Title: Incomplete Data
</span></span><span style="display:flex;"><span>      Type: object truncated due to unexplainable reasons
</span></span><span style="display:flex;"><span>      Description: Summary data only. For complete data, send a specific query for the object.
</span></span><span style="display:flex;"><span>    Link: https://pubapi.registry.google/rdap/nameserver/oaklyn.ns.cloudflare.com
</span></span></code></pre></div>
</details>
<p>Even though <code>rdap</code> works, it isn&rsquo;t installed by default on most systems. Many people are probably going to forget to install <code>rdap</code> or will just default to <code>whois</code> out of habit. I figured that one way to get the old <code>whois</code> command working again is by making a proxy that speaks the WHOIS protocol to the client and will fetch the data using RDAP.</p>
<h3 id="building-a-whois-to-rdap-proxy">Building a WHOIS-to-RDAP Proxy</h3>
<p>Now, I will walk you through a WHOIS server that acts as a proxy to other RDAP servers. It will listen for WHOIS queries on port 43 and when it receives a query, it will make an HTTPS request to the appropriate RDAP server, parse the JSON response, format the important details into a human-readable text format, and send that text back to the original WHOIS client. Simple, no?</p>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/ac8fa3c772e7c2c0a4fb09981f25518ee88a4d296443f2cdfcff620d8ca67ecb.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<p>This approach makes RDAP-only domains accessible to legacy tools that only speak the classic WHOIS protocol. Although, let&rsquo;s be honest, you should probably just use the existing <code>rdap</code> command for anything serious. This is just a toy. But it was fun to make.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/whois-from-scratch/rdap-me-up_hu_2e265a5b7878d6b4.webp" class="center" width="300px"/>
    


<p>Here is the implementation of our new WHOIS-&gt;RDAP proxy server:</p>
<details >
    <summary>
        whois-server-proxy/main.go (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/whois-is-dead/go/whois-server-proxy/main.go" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;bufio&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;encoding/json&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;strings&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;text/template&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	_ <span style="color:#a3be8c">&#34;embed&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#5e81ac;font-style:italic">//go:embed rdap.template</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> rdapTemplateContent <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// tldRdapServers provides a direct mapping for common TLDs to their RDAP servers.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> tldRdapServers <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">map</span><span style="color:#eceff4">[</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">]</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;com&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://rdap.verisign.com/com/v1/domain/&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://rdap.verisign.com/net/v1/domain/&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;org&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://rdap.publicinterestregistry.org/rdap/domain/&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;dev&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;https://pubapi.registry.google/rdap/domain/&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// --- RDAP Data Structures ---</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> RDAPLink <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Rel  <span style="color:#81a1c1">string</span> <span style="color:#a3be8c">`json:&#34;rel&#34;`</span>
</span></span><span style="display:flex;"><span>	Href <span style="color:#81a1c1">string</span> <span style="color:#a3be8c">`json:&#34;href&#34;`</span>
</span></span><span style="display:flex;"><span>	Type <span style="color:#81a1c1">string</span> <span style="color:#a3be8c">`json:&#34;type&#34;`</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> RDAPEvent <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Action <span style="color:#81a1c1">string</span>    <span style="color:#a3be8c">`json:&#34;eventAction&#34;`</span>
</span></span><span style="display:flex;"><span>	Actor  <span style="color:#81a1c1">string</span>    <span style="color:#a3be8c">`json:&#34;eventActor&#34;`</span>
</span></span><span style="display:flex;"><span>	Date   time<span style="color:#eceff4">.</span>Time <span style="color:#a3be8c">`json:&#34;eventDate&#34;`</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> RDAPNameserver <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	LDHName <span style="color:#81a1c1">string</span> <span style="color:#a3be8c">`json:&#34;ldhName&#34;`</span>
</span></span><span style="display:flex;"><span>	Handle  <span style="color:#81a1c1">string</span> <span style="color:#a3be8c">`json:&#34;handle&#34;`</span>
</span></span><span style="display:flex;"><span>	Remarks <span style="color:#eceff4">[]</span><span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Title       <span style="color:#81a1c1">string</span>   <span style="color:#a3be8c">`json:&#34;title&#34;`</span>
</span></span><span style="display:flex;"><span>		Type        <span style="color:#81a1c1">string</span>   <span style="color:#a3be8c">`json:&#34;type&#34;`</span>
</span></span><span style="display:flex;"><span>		Description <span style="color:#eceff4">[]</span><span style="color:#81a1c1">string</span> <span style="color:#a3be8c">`json:&#34;description&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span> <span style="color:#a3be8c">`json:&#34;remarks&#34;`</span>
</span></span><span style="display:flex;"><span>	Links <span style="color:#eceff4">[]</span>RDAPLink <span style="color:#a3be8c">`json:&#34;links&#34;`</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> RDAPEntity <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	VCardArray VCard        <span style="color:#a3be8c">`json:&#34;vcardArray&#34;`</span>
</span></span><span style="display:flex;"><span>	Roles      <span style="color:#eceff4">[]</span><span style="color:#81a1c1">string</span>     <span style="color:#a3be8c">`json:&#34;roles&#34;`</span>
</span></span><span style="display:flex;"><span>	Entities   <span style="color:#eceff4">[]</span>RDAPEntity <span style="color:#a3be8c">`json:&#34;entities&#34;`</span>
</span></span><span style="display:flex;"><span>	Handle     <span style="color:#81a1c1">string</span>       <span style="color:#a3be8c">`json:&#34;handle&#34;`</span>
</span></span><span style="display:flex;"><span>	PublicIDs  <span style="color:#eceff4">[]</span><span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Type       <span style="color:#81a1c1">string</span> <span style="color:#a3be8c">`json:&#34;type&#34;`</span>
</span></span><span style="display:flex;"><span>		Identifier <span style="color:#81a1c1">string</span> <span style="color:#a3be8c">`json:&#34;identifier&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span> <span style="color:#a3be8c">`json:&#34;publicIds&#34;`</span>
</span></span><span style="display:flex;"><span>	Remarks <span style="color:#eceff4">[]</span><span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Title       <span style="color:#81a1c1">string</span>   <span style="color:#a3be8c">`json:&#34;title&#34;`</span>
</span></span><span style="display:flex;"><span>		Type        <span style="color:#81a1c1">string</span>   <span style="color:#a3be8c">`json:&#34;type&#34;`</span>
</span></span><span style="display:flex;"><span>		Description <span style="color:#eceff4">[]</span><span style="color:#81a1c1">string</span> <span style="color:#a3be8c">`json:&#34;description&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span> <span style="color:#a3be8c">`json:&#34;remarks&#34;`</span>
</span></span><span style="display:flex;"><span>	Links  <span style="color:#eceff4">[]</span>RDAPLink <span style="color:#a3be8c">`json:&#34;links&#34;`</span>
</span></span><span style="display:flex;"><span>	Status <span style="color:#eceff4">[]</span><span style="color:#81a1c1">string</span>   <span style="color:#a3be8c">`json:&#34;status&#34;`</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> VCard <span style="color:#eceff4">[]</span><span style="color:#81a1c1;font-weight:bold">interface</span><span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>vc VCard<span style="color:#eceff4">)</span> <span style="color:#88c0d0">GetField</span><span style="color:#eceff4">(</span>key <span style="color:#81a1c1">string</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">string</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>vc<span style="color:#eceff4">)</span> <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">2</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#a3be8c">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	properties<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> vc<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">].([]</span><span style="color:#81a1c1;font-weight:bold">interface</span><span style="color:#eceff4">{})</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#a3be8c">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> _<span style="color:#eceff4">,</span> prop <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> properties <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		propertyArray<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> prop<span style="color:#eceff4">.([]</span><span style="color:#81a1c1;font-weight:bold">interface</span><span style="color:#eceff4">{})</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>ok <span style="color:#81a1c1">||</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>propertyArray<span style="color:#eceff4">)</span> <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">4</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		propKey<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> propertyArray<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">].(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>ok <span style="color:#81a1c1">||</span> propKey <span style="color:#81a1c1">!=</span> key <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		val<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> propertyArray<span style="color:#eceff4">[</span><span style="color:#b48ead">3</span><span style="color:#eceff4">].(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> val
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#a3be8c">&#34;&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> SecureDNSData <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Algorithm  <span style="color:#81a1c1">int</span>    <span style="color:#a3be8c">`json:&#34;algorithm&#34;`</span>
</span></span><span style="display:flex;"><span>	Digest     <span style="color:#81a1c1">string</span> <span style="color:#a3be8c">`json:&#34;digest&#34;`</span>
</span></span><span style="display:flex;"><span>	DigestType <span style="color:#81a1c1">int</span>    <span style="color:#a3be8c">`json:&#34;digestType&#34;`</span>
</span></span><span style="display:flex;"><span>	KeyTag     <span style="color:#81a1c1">int</span>    <span style="color:#a3be8c">`json:&#34;keyTag&#34;`</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> RDAPResponse <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	LDHName     <span style="color:#81a1c1">string</span>           <span style="color:#a3be8c">`json:&#34;ldhName&#34;`</span>
</span></span><span style="display:flex;"><span>	Handle      <span style="color:#81a1c1">string</span>           <span style="color:#a3be8c">`json:&#34;handle&#34;`</span>
</span></span><span style="display:flex;"><span>	Nameservers <span style="color:#eceff4">[]</span>RDAPNameserver <span style="color:#a3be8c">`json:&#34;nameservers&#34;`</span>
</span></span><span style="display:flex;"><span>	Events      <span style="color:#eceff4">[]</span>RDAPEvent      <span style="color:#a3be8c">`json:&#34;events&#34;`</span>
</span></span><span style="display:flex;"><span>	Entities    <span style="color:#eceff4">[]</span>RDAPEntity     <span style="color:#a3be8c">`json:&#34;entities&#34;`</span>
</span></span><span style="display:flex;"><span>	Links       <span style="color:#eceff4">[]</span>RDAPLink       <span style="color:#a3be8c">`json:&#34;links&#34;`</span>
</span></span><span style="display:flex;"><span>	Status      <span style="color:#eceff4">[]</span><span style="color:#81a1c1">string</span>         <span style="color:#a3be8c">`json:&#34;status&#34;`</span>
</span></span><span style="display:flex;"><span>	Conformance <span style="color:#eceff4">[]</span><span style="color:#81a1c1">string</span>         <span style="color:#a3be8c">`json:&#34;rdapConformance&#34;`</span>
</span></span><span style="display:flex;"><span>	Notices     <span style="color:#eceff4">[]</span><span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Title       <span style="color:#81a1c1">string</span>     <span style="color:#a3be8c">`json:&#34;title&#34;`</span>
</span></span><span style="display:flex;"><span>		Description <span style="color:#eceff4">[]</span><span style="color:#81a1c1">string</span>   <span style="color:#a3be8c">`json:&#34;description&#34;`</span>
</span></span><span style="display:flex;"><span>		Links       <span style="color:#eceff4">[]</span>RDAPLink <span style="color:#a3be8c">`json:&#34;links&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span> <span style="color:#a3be8c">`json:&#34;notices&#34;`</span>
</span></span><span style="display:flex;"><span>	SecureDNS <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		ZoneSigned       <span style="color:#81a1c1">bool</span>            <span style="color:#a3be8c">`json:&#34;zoneSigned&#34;`</span>
</span></span><span style="display:flex;"><span>		DelegationSigned <span style="color:#81a1c1">bool</span>            <span style="color:#a3be8c">`json:&#34;delegationSigned&#34;`</span>
</span></span><span style="display:flex;"><span>		DSData           <span style="color:#eceff4">[]</span>SecureDNSData <span style="color:#a3be8c">`json:&#34;dsData&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span> <span style="color:#a3be8c">`json:&#34;secureDNS&#34;`</span>
</span></span><span style="display:flex;"><span>	Remarks <span style="color:#eceff4">[]</span><span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Title       <span style="color:#81a1c1">string</span>   <span style="color:#a3be8c">`json:&#34;title&#34;`</span>
</span></span><span style="display:flex;"><span>		Description <span style="color:#eceff4">[]</span><span style="color:#81a1c1">string</span> <span style="color:#a3be8c">`json:&#34;description&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span> <span style="color:#a3be8c">`json:&#34;remarks&#34;`</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>RDAPResponse<span style="color:#eceff4">)</span> <span style="color:#88c0d0">getReferralURL</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">string</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> _<span style="color:#eceff4">,</span> link <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> r<span style="color:#eceff4">.</span>Links <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> link<span style="color:#eceff4">.</span>Rel <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;related&#34;</span> <span style="color:#81a1c1">&amp;&amp;</span> link<span style="color:#eceff4">.</span>Type <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;application/rdap+json&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> link<span style="color:#eceff4">.</span>Href
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#a3be8c">&#34;&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Server holds the dependencies for the WHOIS server.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> Server <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	rdapTemplate <span style="color:#81a1c1">*</span>template<span style="color:#eceff4">.</span>Template
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// NewServer creates a new server and parses the RDAP template.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">NewServer</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">*</span>Server<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	tmpl<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> template<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;rdap&#34;</span><span style="color:#eceff4">).</span><span style="color:#88c0d0">Parse</span><span style="color:#eceff4">(</span>rdapTemplateContent<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to parse template: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>Server<span style="color:#eceff4">{</span>rdapTemplate<span style="color:#eceff4">:</span> tmpl<span style="color:#eceff4">},</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// queryRDAP performs the RDAP lookup, following one level of referral if necessary.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">queryRDAP</span><span style="color:#eceff4">(</span>domain <span style="color:#81a1c1">string</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">*</span>RDAPResponse<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	parts <span style="color:#81a1c1">:=</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">Split</span><span style="color:#eceff4">(</span>domain<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> url <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>parts<span style="color:#eceff4">)</span> <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">1</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		tld <span style="color:#81a1c1">:=</span> parts<span style="color:#eceff4">[</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>parts<span style="color:#eceff4">)</span><span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> baseUrl<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> tldRdapServers<span style="color:#eceff4">[</span>tld<span style="color:#eceff4">];</span> ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			url <span style="color:#eceff4">=</span> baseUrl <span style="color:#81a1c1">+</span> domain
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Found direct RDAP server for TLD .%s, using: %s&#34;</span><span style="color:#eceff4">,</span> tld<span style="color:#eceff4">,</span> url<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> url <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		url <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;https://rdap.iana.org/domain/&#34;</span> <span style="color:#81a1c1">+</span> domain
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Using IANA bootstrap RDAP endpoint: %s&#34;</span><span style="color:#eceff4">,</span> url<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Perform the initial query</span>
</span></span><span style="display:flex;"><span>	resp<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">Get</span><span style="color:#eceff4">(</span>url<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;RDAP request failed: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> resp<span style="color:#eceff4">.</span>StatusCode <span style="color:#81a1c1">!=</span> http<span style="color:#eceff4">.</span>StatusOK <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		body<span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadAll</span><span style="color:#eceff4">(</span>resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;RDAP server returned status %d: %s&#34;</span><span style="color:#eceff4">,</span> resp<span style="color:#eceff4">.</span>StatusCode<span style="color:#eceff4">,</span> <span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>body<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> initialResponse RDAPResponse
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> json<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewDecoder</span><span style="color:#eceff4">(</span>resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">).</span><span style="color:#88c0d0">Decode</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>initialResponse<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode initial RDAP JSON: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span> <span style="color:#616e87;font-style:italic">// Close the body of the first response now.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Check for a referral and follow it</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> referralURL <span style="color:#81a1c1">:=</span> initialResponse<span style="color:#eceff4">.</span><span style="color:#88c0d0">getReferralURL</span><span style="color:#eceff4">();</span> referralURL <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Following RDAP referral to: %s&#34;</span><span style="color:#eceff4">,</span> referralURL<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		resp<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">Get</span><span style="color:#eceff4">(</span>referralURL<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;RDAP referral request failed: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">defer</span> resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> resp<span style="color:#eceff4">.</span>StatusCode <span style="color:#81a1c1">!=</span> http<span style="color:#eceff4">.</span>StatusOK <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			body<span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadAll</span><span style="color:#eceff4">(</span>resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;RDAP referral server returned status %d: %s&#34;</span><span style="color:#eceff4">,</span> resp<span style="color:#eceff4">.</span>StatusCode<span style="color:#eceff4">,</span> <span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>body<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">var</span> finalResponse RDAPResponse
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> json<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewDecoder</span><span style="color:#eceff4">(</span>resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">).</span><span style="color:#88c0d0">Decode</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>finalResponse<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;failed to decode final RDAP JSON: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>finalResponse<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>initialResponse<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>s <span style="color:#81a1c1">*</span>Server<span style="color:#eceff4">)</span> <span style="color:#88c0d0">handleConnection</span><span style="color:#eceff4">(</span>conn net<span style="color:#eceff4">.</span>Conn<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;new connection from %s&#34;</span><span style="color:#eceff4">,</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">RemoteAddr</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;connection to %s closed&#34;</span><span style="color:#eceff4">,</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">RemoteAddr</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	scanner <span style="color:#81a1c1">:=</span> bufio<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewScanner</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> clientQuery <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> scanner<span style="color:#eceff4">.</span><span style="color:#88c0d0">Scan</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		clientQuery <span style="color:#eceff4">=</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">TrimSpace</span><span style="color:#eceff4">(</span>scanner<span style="color:#eceff4">.</span><span style="color:#88c0d0">Text</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> scanner<span style="color:#eceff4">.</span><span style="color:#88c0d0">Err</span><span style="color:#eceff4">();</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error reading from client: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> clientQuery <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		_<span style="color:#eceff4">,</span> _ <span style="color:#eceff4">=</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fprint</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;Please provide a domain name.\r\n&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Received query for: %q&#34;</span><span style="color:#eceff4">,</span> clientQuery<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	rdapData<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">queryRDAP</span><span style="color:#eceff4">(</span>clientQuery<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;RDAP query for %q failed: %v&#34;</span><span style="color:#eceff4">,</span> clientQuery<span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		_<span style="color:#eceff4">,</span> _ <span style="color:#eceff4">=</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fprintf</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;Error performing RDAP lookup: %v\r\n&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> responseBuilder strings<span style="color:#eceff4">.</span>Builder
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> s<span style="color:#eceff4">.</span>rdapTemplate<span style="color:#eceff4">.</span><span style="color:#88c0d0">Execute</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>responseBuilder<span style="color:#eceff4">,</span> rdapData<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Internal error: failed to execute template: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		_<span style="color:#eceff4">,</span> _ <span style="color:#eceff4">=</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fprint</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;Internal server error.\r\n&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fprint</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">,</span> responseBuilder<span style="color:#eceff4">.</span><span style="color:#88c0d0">String</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error writing response: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	port <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;:43&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	server<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">NewServer</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error creating server: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	listener<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> net<span style="color:#eceff4">.</span><span style="color:#88c0d0">Listen</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;tcp&#34;</span><span style="color:#eceff4">,</span> port<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error listening on port %s: %v&#34;</span><span style="color:#eceff4">,</span> port<span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> listener<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;RDAP Proxy WHOIS server listening on %s&#34;</span><span style="color:#eceff4">,</span> port<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> listener<span style="color:#eceff4">.</span><span style="color:#88c0d0">Accept</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error accepting connection: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">go</span> server<span style="color:#eceff4">.</span><span style="color:#88c0d0">handleConnection</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div>
</details>
<p>The output is formatted using a Go template to create a classic WHOIS-style report from the RDAP JSON data:
<details >
    <summary>
        whois-server-proxy/rdap.template (click to expand)<a href="https://github.com/sudorandom/kmcd.dev/blob/main/posts/2026/whois-is-dead/go/whois-server-proxy/rdap.template" target="_blank" rel="noopener noreferrer" class="details-github-link" title="View on GitHub"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg><span>View on GitHub</span>
            </a></summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>Domain:
</span></span><span style="display:flex;"><span>  Domain Name: {{.LDHName}}
</span></span><span style="display:flex;"><span>  Handle: {{.Handle}}
</span></span><span style="display:flex;"><span>  {{- range .Status}}
</span></span><span style="display:flex;"><span>  Status: {{.}}
</span></span><span style="display:flex;"><span>  {{- end}}
</span></span><span style="display:flex;"><span>  {{- range .Conformance}}
</span></span><span style="display:flex;"><span>  Conformance: {{.}}
</span></span><span style="display:flex;"><span>  {{- end}}
</span></span><span style="display:flex;"><span>  {{- range .Notices}}
</span></span><span style="display:flex;"><span>  Notice:
</span></span><span style="display:flex;"><span>    Title: {{.Title}}
</span></span><span style="display:flex;"><span>    {{- range .Description}}
</span></span><span style="display:flex;"><span>    Description: {{.}}
</span></span><span style="display:flex;"><span>    {{- end}}
</span></span><span style="display:flex;"><span>	{{- range .Links}}
</span></span><span style="display:flex;"><span>    Link: {{.Href}}
</span></span><span style="display:flex;"><span>    {{- end}}
</span></span><span style="display:flex;"><span>  {{- end}}
</span></span><span style="display:flex;"><span>  {{- range .Links}}
</span></span><span style="display:flex;"><span>  Link: {{.Href}}
</span></span><span style="display:flex;"><span>  {{- end}}
</span></span><span style="display:flex;"><span>  {{- range .Events}}
</span></span><span style="display:flex;"><span>  Event:
</span></span><span style="display:flex;"><span>    Action: {{.Action}}
</span></span><span style="display:flex;"><span>	{{- if .Actor}}
</span></span><span style="display:flex;"><span>    Actor: {{.Actor}}
</span></span><span style="display:flex;"><span>	{{- end}}
</span></span><span style="display:flex;"><span>    Date: {{.Date.Format &#34;2006-01-02T15:04:05.000Z&#34;}}
</span></span><span style="display:flex;"><span>  {{- end}}
</span></span><span style="display:flex;"><span>  {{- with .SecureDNS}}
</span></span><span style="display:flex;"><span>  Secure DNS:
</span></span><span style="display:flex;"><span>    Zone Signed: {{.ZoneSigned}}
</span></span><span style="display:flex;"><span>    Delegation Signed: {{.DelegationSigned}}
</span></span><span style="display:flex;"><span>	{{- range .DSData}}
</span></span><span style="display:flex;"><span>    DSData:
</span></span><span style="display:flex;"><span>      Key Tag: {{.KeyTag}}
</span></span><span style="display:flex;"><span>      Algorithm: {{.Algorithm}}
</span></span><span style="display:flex;"><span>      Digest: {{.Digest}}
</span></span><span style="display:flex;"><span>      DigestType: {{.DigestType}}
</span></span><span style="display:flex;"><span>	{{- end}}
</span></span><span style="display:flex;"><span>  {{- end}}
</span></span><span style="display:flex;"><span>  {{- range .Entities}}
</span></span><span style="display:flex;"><span>  Entity:
</span></span><span style="display:flex;"><span>    Handle: {{.Handle}}
</span></span><span style="display:flex;"><span>	{{- range .PublicIDs}}
</span></span><span style="display:flex;"><span>    Public ID:
</span></span><span style="display:flex;"><span>      Type: {{.Type}}
</span></span><span style="display:flex;"><span>      Identifier: {{.Identifier}}
</span></span><span style="display:flex;"><span>	{{- end}}
</span></span><span style="display:flex;"><span>	{{- range .Remarks}}
</span></span><span style="display:flex;"><span>    Remark:
</span></span><span style="display:flex;"><span>      Title: {{.Title}}
</span></span><span style="display:flex;"><span>      Type: {{.Type}}
</span></span><span style="display:flex;"><span>	  {{- range .Description}}
</span></span><span style="display:flex;"><span>      Description: {{.}}
</span></span><span style="display:flex;"><span>	  {{- end}}
</span></span><span style="display:flex;"><span>	{{- end}}
</span></span><span style="display:flex;"><span>	{{- range .Links}}
</span></span><span style="display:flex;"><span>    Link: {{.Href}}
</span></span><span style="display:flex;"><span>	{{- end}}
</span></span><span style="display:flex;"><span>    Role: {{range $i, $role := .Roles}}{{if $i}}, {{end}}{{$role}}{{end}}
</span></span><span style="display:flex;"><span>	{{- if .VCardArray.GetField &#34;fn&#34;}}
</span></span><span style="display:flex;"><span>    vCard version: 4.0
</span></span><span style="display:flex;"><span>    vCard fn: {{.VCardArray.GetField &#34;fn&#34;}}
</span></span><span style="display:flex;"><span>	{{- end}}
</span></span><span style="display:flex;"><span>	{{- range .Entities}}
</span></span><span style="display:flex;"><span>    Entity:
</span></span><span style="display:flex;"><span>	  {{- range .Status}}
</span></span><span style="display:flex;"><span>      Status: {{.}}
</span></span><span style="display:flex;"><span>	  {{- end}}
</span></span><span style="display:flex;"><span>      Role: {{range $i, $role := .Roles}}{{if $i}}, {{end}}{{$role}}{{end}}
</span></span><span style="display:flex;"><span>      vCard version: 4.0
</span></span><span style="display:flex;"><span>      vCard fn: {{.VCardArray.GetField &#34;fn&#34;}}
</span></span><span style="display:flex;"><span>      vCard tel: {{.VCardArray.GetField &#34;tel&#34;}}
</span></span><span style="display:flex;"><span>      vCard email: {{.VCardArray.GetField &#34;email&#34;}}
</span></span><span style="display:flex;"><span>	{{- end}}
</span></span><span style="display:flex;"><span>  {{- end}}
</span></span><span style="display:flex;"><span>  {{- range .Nameservers}}
</span></span><span style="display:flex;"><span>  Nameserver:
</span></span><span style="display:flex;"><span>    Nameserver: {{.LDHName}}
</span></span><span style="display:flex;"><span>    Handle: {{.Handle}}
</span></span><span style="display:flex;"><span>	{{- range .Remarks}}
</span></span><span style="display:flex;"><span>    Remark:
</span></span><span style="display:flex;"><span>      Title: {{.Title}}
</span></span><span style="display:flex;"><span>      Type: {{.Type}}
</span></span><span style="display:flex;"><span>	  {{- range .Description}}
</span></span><span style="display:flex;"><span>      Description: {{.}}
</span></span><span style="display:flex;"><span>	  {{- end}}
</span></span><span style="display:flex;"><span>	{{- end}}
</span></span><span style="display:flex;"><span>	{{- range .Links}}
</span></span><span style="display:flex;"><span>    Link: {{.Href}}
</span></span><span style="display:flex;"><span>	{{- end}}
</span></span><span style="display:flex;"><span>  {{- end}}
</span></span></code></pre></div>
</details></p>
<p>With the proxy running, we can query it for <code>kmcd.dev</code> and get a complete and useful response using a standard <code>whois</code> client:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Run the proxy in one terminal</span>
</span></span><span style="display:flex;"><span>go run ./whois-server-proxy
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Query it from another</span>
</span></span><span style="display:flex;"><span>whois -h localhost kmcd.dev
</span></span></code></pre></div><details >
    <summary>
        Output (click to expand)</summary>
    <div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Domain:
</span></span><span style="display:flex;"><span>  Domain Name: kmcd.dev
</span></span><span style="display:flex;"><span>  Handle: E04E36511-DEV
</span></span><span style="display:flex;"><span>  Status: client transfer prohibited
</span></span><span style="display:flex;"><span>  Conformance: rdap_level_0
</span></span><span style="display:flex;"><span>  Conformance: icann_rdap_technical_implementation_guide_0
</span></span><span style="display:flex;"><span>  Conformance: icann_rdap_response_profile_0
</span></span><span style="display:flex;"><span>  Notice:
</span></span><span style="display:flex;"><span>    Title: Cloudflare Registrar
</span></span><span style="display:flex;"><span>    Description: Cloudflare provides more than 13 million domains with the tools to give their global users a faster, more secure, and more reliable internet experience.
</span></span><span style="display:flex;"><span>    Description: Register your domain name at https://www.cloudflare.com/registrar/
</span></span><span style="display:flex;"><span>    Link: https://www.cloudflare.com/registrar/
</span></span><span style="display:flex;"><span>  Notice:
</span></span><span style="display:flex;"><span>    Title: Terms of Use
</span></span><span style="display:flex;"><span>    Description: Data in the Cloudflare Registrar WHOIS database is provided to you by Cloudflare under the terms and conditions at https://www.cloudflare.com/domain-registration-agreement/.
</span></span><span style="display:flex;"><span>    Description: By submitting this query, you agree to abide by these terms.
</span></span><span style="display:flex;"><span>    Link: https://www.cloudflare.com/domain-registration-agreement/
</span></span><span style="display:flex;"><span>  Notice:
</span></span><span style="display:flex;"><span>    Title: EPP Status Codes
</span></span><span style="display:flex;"><span>    Description: For more information on domain status codes, please visit https://icann.org/epp.
</span></span><span style="display:flex;"><span>    Link: https://icann.icann.org/epp
</span></span><span style="display:flex;"><span>  Notice:
</span></span><span style="display:flex;"><span>    Title: Whois Inaccuracy Complaint Form
</span></span><span style="display:flex;"><span>    Description: URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf.
</span></span><span style="display:flex;"><span>    Link: https://www.icann.org/wicf
</span></span><span style="display:flex;"><span>  Event:
</span></span><span style="display:flex;"><span>    Action: registration
</span></span><span style="display:flex;"><span>    Date: 2024-05-18T19:33:01.000Z
</span></span><span style="display:flex;"><span>  Event:
</span></span><span style="display:flex;"><span>    Action: last changed
</span></span><span style="display:flex;"><span>    Date: 2024-05-23T20:08:26.329Z
</span></span><span style="display:flex;"><span>  Event:
</span></span><span style="display:flex;"><span>    Action: expiration
</span></span><span style="display:flex;"><span>    Date: 2034-05-18T19:33:01.000Z
</span></span><span style="display:flex;"><span>  Event:
</span></span><span style="display:flex;"><span>    Action: registrar expiration
</span></span><span style="display:flex;"><span>    Date: 2034-05-18T19:33:01.000Z
</span></span><span style="display:flex;"><span>  Secure DNS:
</span></span><span style="display:flex;"><span>    Zone Signed: false
</span></span><span style="display:flex;"><span>    Delegation Signed: true
</span></span><span style="display:flex;"><span>    DSData:
</span></span><span style="display:flex;"><span>      Key Tag: 2371
</span></span><span style="display:flex;"><span>      Algorithm: 13
</span></span><span style="display:flex;"><span>      Digest: 321F88BA26AB76AE885C06671798645ADF4D06448F52D67D627641FA28392AF7
</span></span><span style="display:flex;"><span>      DigestType: 2
</span></span><span style="display:flex;"><span>  Entity:
</span></span><span style="display:flex;"><span>    Handle: 1910
</span></span><span style="display:flex;"><span>    Public ID:
</span></span><span style="display:flex;"><span>      Type: IANA Registrar ID
</span></span><span style="display:flex;"><span>      Identifier: 1910
</span></span><span style="display:flex;"><span>    Role: registrar
</span></span><span style="display:flex;"><span>    vCard version: 4.0
</span></span><span style="display:flex;"><span>    vCard fn: Cloudflare, Inc.
</span></span><span style="display:flex;"><span>    Entity:
</span></span><span style="display:flex;"><span>      Role: abuse
</span></span><span style="display:flex;"><span>      vCard version: 4.0
</span></span><span style="display:flex;"><span>      vCard fn: Cloudflare Registrar Abuse
</span></span><span style="display:flex;"><span>      vCard tel: tel:+1.4153197517
</span></span><span style="display:flex;"><span>      vCard email: registrar-abuse@cloudflare.com
</span></span><span style="display:flex;"><span>  Entity:
</span></span><span style="display:flex;"><span>    Handle:
</span></span><span style="display:flex;"><span>    Remark:
</span></span><span style="display:flex;"><span>      Title: DATA REDACTED
</span></span><span style="display:flex;"><span>      Type: object redacted due to authorization
</span></span><span style="display:flex;"><span>      Description: Some of the data in this object has been removed
</span></span><span style="display:flex;"><span>    Role: registrant
</span></span><span style="display:flex;"><span>    vCard version: 4.0
</span></span><span style="display:flex;"><span>    vCard fn: DATA REDACTED
</span></span><span style="display:flex;"><span>  Entity:
</span></span><span style="display:flex;"><span>    Handle:
</span></span><span style="display:flex;"><span>    Remark:
</span></span><span style="display:flex;"><span>      Title: DATA REDACTED
</span></span><span style="display:flex;"><span>      Type: object redacted due to authorization
</span></span><span style="display:flex;"><span>      Description: Some of the data in this object has been removed
</span></span><span style="display:flex;"><span>    Role: administrative
</span></span><span style="display:flex;"><span>    vCard version: 4.0
</span></span><span style="display:flex;"><span>    vCard fn: DATA REDACTED
</span></span><span style="display:flex;"><span>  Entity:
</span></span><span style="display:flex;"><span>    Handle:
</span></span><span style="display:flex;"><span>    Remark:
</span></span><span style="display:flex;"><span>      Title: DATA REDACTED
</span></span><span style="display:flex;"><span>      Type: object redacted due to authorization
</span></span><span style="display:flex;"><span>      Description: Some of the data in this object has been removed
</span></span><span style="display:flex;"><span>    Role: technical
</span></span><span style="display:flex;"><span>    vCard version: 4.0
</span></span><span style="display:flex;"><span>    vCard fn: DATA REDACTED
</span></span><span style="display:flex;"><span>  Entity:
</span></span><span style="display:flex;"><span>    Handle:
</span></span><span style="display:flex;"><span>    Remark:
</span></span><span style="display:flex;"><span>      Title: DATA REDACTED
</span></span><span style="display:flex;"><span>      Type: object redacted due to authorization
</span></span><span style="display:flex;"><span>      Description: Some of the data in this object has been removed
</span></span><span style="display:flex;"><span>    Role: billing
</span></span><span style="display:flex;"><span>    vCard version: 4.0
</span></span><span style="display:flex;"><span>    vCard fn: DATA REDACTED
</span></span><span style="display:flex;"><span>  Nameserver:
</span></span><span style="display:flex;"><span>    Nameserver: chuck.ns.cloudflare.com
</span></span><span style="display:flex;"><span>    Handle:
</span></span><span style="display:flex;"><span>  Nameserver:
</span></span><span style="display:flex;"><span>    Nameserver: oaklyn.ns.cloudflare.com
</span></span><span style="display:flex;"><span>    Handle:
</span></span></code></pre></div>
</details>
<h3 id="closing-thoughts">Closing Thoughts</h3>
<p>WHOIS is simple and approachable, but it belongs to a smaller and more trusting Internet. It relies on unstructured text, inconsistent formatting, informal conventions, and an unencrypted transport. It is out-of-place in the modern Internet.</p>
<p>RDAP is the natural evolution of WHOIS. It fixes the exact problems that made WHOIS brittle: structure, discovery and security.</p>
<p>By wrapping the new standard in the old interface, we bridged the gap between the past and present. With the WHOIS-to-RDAP proxy, we get the structured power of RDAP without losing the muscle-memory and intuitive naming of the <code>whois</code> command.</p>
<p>This was a toy project made to learn about both WHOIS and RDAP, but this acts as a useful lens on how internet protocols evolve and illustrates many features of modern web APIs that we take for granted today.</p>
<h3 id="references">References</h3>
<ul>
<li><a href="https://www.rfc-editor.org/rfc/rfc3912" rel="external">RFC 3912 - WHOIS Protocol Specification</a></li>
<li><a href="https://en.wikipedia.org/wiki/WHOIS" rel="external">WHOIS (Wikipedia)</a></li>
<li><a href="https://www.rfc-editor.org/rfc/rfc7480" rel="external">RDAP (Registration Data Access Protocol)</a></li>
</ul>
]]></content:encoded></item><item><title>Months Considered Harmful</title><link>https://kmcd.dev/posts/months-considered-harmful/</link><pubDate>Thu, 01 Jan 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/months-considered-harmful/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/months-considered-harmful/cover.svg" /> &lt;/p>
                
                Can months be fixed?
                </description><content:encoded><![CDATA[<p>The calendar we all live by is a shambling mess. It has arbitrary month lengths, misnamed months, and rules so absurd we rely on nursery rhymes just to remember them. For something that structures our lives, it is clunky and illogical. This post is my attempt to design something that actually makes sense.</p>
<h2 id="whats-wrong-with-our-calendar">What&rsquo;s wrong with our calendar?</h2>
<p>Our current calendar is far from optimal. Months have varying numbers of days, which isn&rsquo;t just annoying; it makes planning harder. For example, budgeting is made harder because month lengths don&rsquo;t just vary in days, but in the number of pay periods or expense cycles they contain, leading to unpredictable cash flow. This may seem trivial, but it&rsquo;s a strange flaw in a system we use every day.</p>
<p>Built through centuries of compromises and quick fixes, what began as lunar tracking has decayed into something that does neither job well. While not directly relevant to most modern daily planning, the conceptual failure to maintain a consistent natural rhythm highlights the calendar&rsquo;s fundamental breakage from its origins. This breakdown stems from prioritizing the solar year for stable seasons and holidays, an aim that directly conflicts with the irregular lunar cycle.</p>
<p>The naming convention is another glaring bug. Consider September, October, November, and December. Their Latin roots (<em>septem</em> (7), <em>octo</em> (8), <em>novem</em> (9), and <em>decem</em> (10)) clearly point to their original positions. Yet, today they are our 9th, 10th, 11th, and 12th months. This historical mess is the result of early Roman calendar reforms, where the start of the year was shifted, leaving several months with names that no longer match their position.</p>
<p>So what are we left with?</p>
<ul>
<li>Months are inconsistent in length.</li>
<li>The naming of months is inconsistent with their meaning.</li>
<li>We have vestigial concepts that have lost their ties to their original purpose.</li>
</ul>
<h2 id="part-i-13-month-year">Part I: 13-Month Year</h2>
<p>My first pass at a fix was all about logic. I designed a clean, predictable system based on a perfect grid.</p>
<h3 id="the-spec-a-perfect-grid"><strong>The Spec: A Perfect Grid</strong></h3>
<p>The idea was a perpetual calendar:</p>
<ul>
<li><strong>13 Months,</strong> each 28 days long.</li>
<li><strong>4 Perfect Weeks</strong> per month, starting on a Monday and ending on a Sunday. Always.</li>
<li><strong>1 &ldquo;Year Day&rdquo;</strong> at the end. <code>13 × 28 = 364</code>. The 365th day is a special holiday outside the weekly cycle, with a second &ldquo;Leap Day&rdquo; when we need it.</li>
</ul>
<p>To see the difference, compare a perfect 13th-month calendar month with our current January 2026.</p>
<p><strong>&ldquo;Perfect&rdquo; Month</strong></p>
<table>
  <thead>
      <tr>
          <th>Mon</th>
          <th>Tue</th>
          <th>Wed</th>
          <th>Thu</th>
          <th>Fri</th>
          <th>Sat</th>
          <th>Sun</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>2</td>
          <td>3</td>
          <td>4</td>
          <td>5</td>
          <td>6</td>
          <td>7</td>
      </tr>
      <tr>
          <td>8</td>
          <td>9</td>
          <td>10</td>
          <td>11</td>
          <td>12</td>
          <td>13</td>
          <td>14</td>
      </tr>
      <tr>
          <td>15</td>
          <td>16</td>
          <td>17</td>
          <td>18</td>
          <td>19</td>
          <td>20</td>
          <td>21</td>
      </tr>
      <tr>
          <td>22</td>
          <td>23</td>
          <td>24</td>
          <td>25</td>
          <td>26</td>
          <td>27</td>
          <td>28</td>
      </tr>
  </tbody>
</table>
<p><strong>January 2026</strong></p>
<table>
  <thead>
      <tr>
          <th>Mon</th>
          <th>Tue</th>
          <th>Wed</th>
          <th>Thu</th>
          <th>Fri</th>
          <th>Sat</th>
          <th>Sun</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td></td>
          <td></td>
          <td></td>
          <td>1</td>
          <td>2</td>
          <td>3</td>
          <td>4</td>
      </tr>
      <tr>
          <td>5</td>
          <td>6</td>
          <td>7</td>
          <td>8</td>
          <td>9</td>
          <td>10</td>
          <td>11</td>
      </tr>
      <tr>
          <td>12</td>
          <td>13</td>
          <td>14</td>
          <td>15</td>
          <td>16</td>
          <td>17</td>
          <td>18</td>
      </tr>
      <tr>
          <td>19</td>
          <td>20</td>
          <td>21</td>
          <td>22</td>
          <td>23</td>
          <td>24</td>
          <td>25</td>
      </tr>
      <tr>
          <td>26</td>
          <td>27</td>
          <td>28</td>
          <td>29</td>
          <td>30</td>
          <td>31</td>
          <td></td>
      </tr>
  </tbody>
</table>
<p>The appeal is its deterministic nature. The 10th of <em>any</em> month is always a Wednesday. Annoying date arithmetic for things like budgets, sprints, and payroll just becomes simple. It’s a clean API for time.</p>
<h3 id="where-my-dream-falls-apart">Where My Dream Falls Apart</h3>
<p>Of course, for all its elegance on paper, this system slams into a wall of bitter reality.</p>
<p>The entire global economy runs on four quarters. <code>13</code> is a prime number, meaning you can&rsquo;t divide it cleanly into equal quarters. While individuals don&rsquo;t live their lives in quarters, corporations do. This would necessitate creating awkward <code>3-3-3-4</code> month quarters for financial reporting, which ultimately reintroduces the very inconsistency we were attempting to eliminate. Economic reality kills calendar idealism.</p>
<p>Implementing such a calendar change would be a monumental undertaking, akin to Y2K on a global scale but vastly more complex due to deeply embedded legal, civil, and international coordination challenges. Every piece of software, every legal contract, and every database on Earth would require extensive rewriting. The associated cost would be unfathomable and would ultimately outweigh any benefits that we&rsquo;d gain.</p>
<p>Introducing a &ldquo;timeless&rdquo; day (or two on leap years) into the calendar disrupts the continuous seven-day weekly cycle. It is&hellip; awkward. How would you write the date for this day? This disruption would break critical real-world systems like payroll cycles, recurring shifts, and automated cron jobs that rely on an unbroken weekly sequence. Some people still have to work, so there would be a weird exception for planning for this day. Timekeeping software would need to specifically handle this.</p>
<p>After considering the downsides, I have decided that my dream will continue to stay a dream. Months cannot be fixed&hellip; at least not without creating more problems. Who really needs months anyway?</p>
<hr>
<h3 id="part-ii-deprecating-the-month">Part II: Deprecating the Month</h3>
<p>Why do we use months at all? What purpose do they serve in our daily lives? They&rsquo;re a mashup of bad Latin, Roman vanity, and arbitrary divisions that have no real connection to the 7-day cycle that actually governs our schedules. The bug isn&rsquo;t the <em>implementation</em> of the month. The month <em>itself</em> is the bug.</p>
<p>So, let&rsquo;s deprecate the abstraction.</p>
<h3 id="long-live-the-week">Long live the week</h3>
<p>The proposal is to use the <strong>week</strong> as the main unit of time beyond the day. A year is just 52 weeks. And get this: this isn&rsquo;t some new idea I dreamed up. It&rsquo;s already part of the <strong>ISO 8601 international standard</strong>.</p>
<p>The formats are clean and unambiguous:</p>
<ul>
<li><strong>Week:</strong> <code>YYYY-Www</code>. Example: <strong><code>2025-W41</code></strong> represents the 41st week of 2025.</li>
<li><strong>Week with weekday:</strong> <code>YYYY-Www-D</code>. Example: <strong><code>2025-W41-2</code></strong> represents the 2nd day (Tuesday) of the 41st week of 2025. (Monday is 1, Sunday is 7).</li>
</ul>
<p>The system is built and standardized; we just don&rsquo;t use it as our default. Here’s how the first few weeks of 2026 look using the ISO 8601 week-date system, with the Gregorian date (day/month) for comparison.</p>
<table>
  <thead>
      <tr>
          <th>ISO Week</th>
          <th>Mon</th>
          <th>Tue</th>
          <th>Wed</th>
          <th>Thu</th>
          <th>Fri</th>
          <th>Sat</th>
          <th>Sun</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>2026-W01</strong></td>
          <td>29/12</td>
          <td>30/12</td>
          <td>31/12</td>
          <td>01/01</td>
          <td>02/01</td>
          <td>03/01</td>
          <td>04/01</td>
      </tr>
      <tr>
          <td><strong>2026-W02</strong></td>
          <td>05/01</td>
          <td>06/01</td>
          <td>07/01</td>
          <td>08/01</td>
          <td>09/01</td>
          <td>10/01</td>
          <td>11/01</td>
      </tr>
      <tr>
          <td><strong>2026-W03</strong></td>
          <td>12/01</td>
          <td>13/01</td>
          <td>14/01</td>
          <td>15/01</td>
          <td>16/01</td>
          <td>17/01</td>
          <td>18/01</td>
      </tr>
      <tr>
          <td><strong>2026-W04</strong></td>
          <td>19/01</td>
          <td>20/01</td>
          <td>21/01</td>
          <td>22/01</td>
          <td>23/01</td>
          <td>24/01</td>
          <td>25/01</td>
      </tr>
      <tr>
          <td><strong>2026-W05</strong></td>
          <td>26/01</td>
          <td>27/01</td>
          <td>28/01</td>
          <td>29/01</td>
          <td>30/01</td>
          <td>31/01</td>
          <td>01/02</td>
      </tr>
  </tbody>
</table>
<p>Note that week 1 of 2026 starts in December of 2025. This might seem confusing, but it’s a deliberate design choice. Rule of thumb: <strong>The first week of the year is the week that has the year&rsquo;s first Thursday in it.</strong> This is the standard definition and explains why the week can straddle years—it ensures the majority of the week belongs to the new year. It&rsquo;s a trade-off: the Gregorian calendar breaks weekday consistency at the year&rsquo;s boundary, while the week-date system prioritizes the weekly cycle <em>over</em> the year boundary. In practice, you often stop caring which &lsquo;year&rsquo; a Monday belongs to, because your planning horizon is almost always weeks ahead, not abstract year boundaries.</p>
<h3 id="addressing-the-concerns">Addressing the Concerns</h3>
<p>This idea has its own set of hurdles, but they feel more like matters of habit than hard blockers.</p>
<p>The fiscal quarter problem is solved. Remember how the 13-month calendar failed because 13 is a prime number? The week-based system fixes this beautifully. A 52-week year divides perfectly into four 13-week quarters. This is already a common practice in many industries (often called a 4-4-5 calendar) because it makes financial reporting and year-over-year comparisons much more consistent. Far from being a problem, this is a major advantage.</p>
<p>Months are poor seasonal anchors anyway. &ldquo;December&rdquo; is winter here in Copenhagen but high summer in Cape Town. Tying months to seasons is just a Northern Hemisphere bias. We can create new narratives. The argument that we need months for &ldquo;chapter breaks&rdquo; in our year assumes we&rsquo;re incapable of making new patterns. We could easily create new milestones around 10-week blocks or the four 13-week quarters. The narrative doesn&rsquo;t disappear; it just gets refactored.</p>
<p>As I&rsquo;ve learned living in Denmark, using week numbers for planning can be completely normal. It coexists with the standard calendar. I&rsquo;ve had people invite me to events in &ldquo;week 45.&rdquo; The first time, I had to look it up. The fifth time, I also had to look it up. But I assume that with enough usage I will build an intuition.</p>
<h2 id="the-migration-path-an-evolution">The Migration Path: An Evolution</h2>
<p>The end result of this thought exercise is a new year years goal for myself: I&rsquo;m going to lean into using week numbers more in my own life. This isn&rsquo;t a radical proposition; it&rsquo;s about consciously adopting a better system that already exists in parallel. It’s an evolutionary change, and here’s how you can start:</p>
<p><strong>Add Week Numbers to Your Calendar.</strong> Most digital calendars support this as a display option. Making the week number visible is the first step to making it intuitive. Try it for three months.</p>
<p><strong>Start Using Weeks in Planning.</strong> In your next meeting invite, add the week number. &ldquo;Let&rsquo;s sync up in Week 45.&rdquo; Will people look at you weird when you ask that? Probably. But notice how often months stop mattering in your planning.</p>
<p>By pushing for a better standard that <em>already exists</em>, maybe you can start the slow process of deprecating using months in favor of weeks in your own life. Happy week 1, everyone!</p>
<hr>
<h2 id="references--further-reading">References &amp; Further Reading</h2>
<p>For those interested in a deeper dive into the topics discussed, here are some of the resources used in researching this article:</p>
<ul>
<li>
<p><strong>On the History of the Roman and Gregorian Calendars:</strong></p>
<ul>
<li>The Editors of Encyclopaedia Britannica. <a href="https://www.britannica.com/science/Roman-republican-calendar" rel="external">&ldquo;Roman Republican calendar&rdquo;</a>. <em>Britannica</em>.</li>
<li><a href="https://www.almanac.com/content/origin-month-names" rel="external">&ldquo;The Origin of the Month Names&rdquo;</a>. <em>Almanac.com</em>.</li>
</ul>
</li>
<li>
<p><strong>On Proposed Alternative Calendars:</strong></p>
<ul>
<li><a href="https://en.wikipedia.org/wiki/International_Fixed_Calendar" rel="external">&ldquo;International Fixed Calendar&rdquo;</a>. <em>Wikipedia</em>.</li>
<li>Aldrich, Jeremy. <a href="https://jeremy-aldrich.com/the-international-fixed-calendar/" rel="external">&ldquo;The International Fixed Calendar&rdquo;</a>.</li>
</ul>
</li>
<li>
<p><strong>On the ISO 8601 Week-Date System:</strong></p>
<ul>
<li><a href="https://en.wikipedia.org/wiki/ISO_week_date" rel="external">&ldquo;ISO week date&rdquo;</a>. <em>Wikipedia</em>.</li>
</ul>
</li>
<li>
<p><strong>On Lunar vs. Solar Calendars:</strong></p>
<ul>
<li>Taylor, Elise. <a href="https://sciencing.com/what-is-the-difference-between-a-lunar-a-solar-calendar-13710243.html" rel="external">&ldquo;What Is the Difference Between a Lunar &amp; a Solar Calendar?&rdquo;</a>. <em>Sciencing</em>.</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li><strong>On the 4-4-5 Calendar:</strong>
<ul>
<li><a href="https://en.wikipedia.org/wiki/4-4-5_calendar" rel="external">&ldquo;4-4-5 Calendar&rdquo;</a>. <em>Wikipedia</em>.</li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title>Encryption vs. Compression</title><link>https://kmcd.dev/posts/encryption-vs-compression/</link><pubDate>Mon, 22 Dec 2025 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/encryption-vs-compression/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/encryption-vs-compression/cover.svg" /> &lt;/p>
                
                Is there a correct order when encrypting and compressing data?
                </description><content:encoded><![CDATA[<p>Compression shrinks data, encryption obfuscates it. Should you compress or encrypt first? If you get this wrong and you will waste CPU, storage, and bandwidth. Pick wisely.</p>
<span class="bigtext"><strong>Always compress first, then encrypt.</strong></span>
<p>In backups, data pipelines, secure transport, or any place where performance matters, picking the wrong order will waste a lot of resources needlessly. I often ask this exact question during interviews. The question reveals more than just memorization of trivia; it shows whether they truly understand how compression and encryption work.</p>
<h3 id="why-the-order-matters">Why the order matters</h3>
<p>The reason &ldquo;encrypt then compress&rdquo; is a terrible idea is due to the opposing goals of the two operations.</p>
<p><strong>Compression tools are pattern-finders.</strong> A tool like <code>gzip</code> hunts for repeated sequences in your file, like a recurring log message or timestamp, and replaces those long strings with tiny references. It works best with order and predictability.</p>
<p><strong>Encryption tools are pattern-destroyers.</strong> The goal of a strong encryption algorithm (like AES) is to scramble your orderly data into high-entropy, random-looking noise. A perfectly encrypted file should have no predictable patterns.</p>
<p>So, if you <strong>encrypt first</strong>, you&rsquo;re feeding random noise to a compression tool that thrives on patterns. It finds none, gives up, and the file barely shrinks (or might even get slightly larger).</p>
<p>But if you <strong>compress first</strong>, the algorithm gets to work on the raw, patterned data. It shrinks it way down. <em>Then</em> you encrypt the much smaller result.</p>
<h3 id="lets-try-it-out">Let&rsquo;s try it out</h3>
<p>Instead of just talking about it, let&rsquo;s run a small experiment to demonstrate what happens when you get the order wrong. We&rsquo;ll run both pipelines side by side so you can see how the output sizes change. We can prove this in about thirty seconds with standard command-line tools you likely already have installed. We&rsquo;ll use <code>gzip</code> for compression and <code>gpg</code> for encryption.</p>
<p>First, we need some data to play with. Log files are perfect candidates since they&rsquo;re so repetitive. Instead of using a real system log, we&rsquo;ll generate our own super-compressible file.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Create a dummy log file that&#39;s roughly 6MB.</span>
</span></span><span style="display:flex;"><span>yes <span style="color:#a3be8c">&#34;this is a line in our log file&#34;</span> <span style="color:#eceff4">|</span> head -n <span style="color:#b48ead">200000</span> &gt; log.txt
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># (`yes` just prints the same line endlessly — we use `head` to cap it.)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Check its size</span>
</span></span><span style="display:flex;"><span>ls -lh log.txt
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Output: -rw-r--r--@ 1 kevin  staff   5.9M Dec 12 17:13 log.txt</span>
</span></span></code></pre></div><p>With <code>log.txt</code> ready, it&rsquo;s time to experiment.</p>
<h4 id="the-wrong-way-encrypt--compress">The Wrong Way: Encrypt → Compress</h4>
<p>Here, we&rsquo;ll encrypt the file and <em>then</em> try to compress the result. But there&rsquo;s a catch: modern <code>gpg</code> is smart and compresses data by default. We have to tell it not to with the <code>--compress-algo none</code> flag to truly see the wrong way in action.</p>
<p><strong>What you run</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># 1. Encrypt the log file (with GPG&#39;s internal compression DISABLED)</span>
</span></span><span style="display:flex;"><span>gpg <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --batch --yes <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --pinentry-mode loopback <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --passphrase <span style="color:#a3be8c">&#34;test&#34;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --compress-algo none <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --symmetric <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --output encrypted_first.gpg <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  log.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># 2. Now, try to compress the encrypted file</span>
</span></span><span style="display:flex;"><span>gzip -c encrypted_first.gpg &gt; encrypted_then_compressed.gz
</span></span></code></pre></div><p><strong>What you see</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ls -lh log.txt encrypted_first.gpg encrypted_then_compressed.gz
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Output:</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># -rw-r--r--@ 1 kevin  staff   5.9M Dec 12 17:13 log.txt</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># -rw-r--r--@ 1 kevin  staff   5.9M Dec 12 18:14 encrypted_first.gpg</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># -rw-r--r--@ 1 kevin  staff   5.9M Dec 12 18:14 encrypted_then_compressed.gz</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ls -l log.txt encrypted_first.gpg encrypted_then_compressed.gz
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Output:</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># -rw-r--r--@ 1 kevin  staff  6200000 Dec 12 17:13 log.txt</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># -rw-r--r--@ 1 kevin  staff  6200084 Dec 12 18:14 encrypted_first.gpg</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># -rw-r--r--@ 1 kevin  staff  6202017 Dec 12 18:14 encrypted_then_compressed.gz</span>
</span></span></code></pre></div><p>The file size didn&rsquo;t reduce with this setup; it <em><strong>actually increased</strong></em> by 2 kilobytes.</p>
<p><strong>Encryption produces high-entropy data, so compression has nothing to work with.</strong></p>
<p><strong>Why it matters</strong>
The <code>encrypted_first.gpg</code> file is the same size as the original, and <code>encrypted_then_compressed.gz</code> is&hellip; also the same size. The compression tool looked at the encrypted gibberish, found no patterns, and gave up. A complete waste of effort</p>
<h4 id="the-right-way-compress--encrypt">The Right Way: Compress → Encrypt</h4>
<p>Now, let&rsquo;s do it correctly. We&rsquo;ll compress the file first, then encrypt the much smaller result.</p>
<p><strong>What you run</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># 1. Compress the raw log file</span>
</span></span><span style="display:flex;"><span>gzip -c log.txt &gt; compressed_first.gz
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># 2. Encrypt the compressed file</span>
</span></span><span style="display:flex;"><span>gpg <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --batch --yes <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --pinentry-mode loopback <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --passphrase <span style="color:#a3be8c">&#34;test&#34;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --symmetric <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --output compressed_then_encrypted.gpg <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  compressed_first.gz
</span></span></code></pre></div><p><strong>What you see</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ls -lh log.txt compressed_first.gz compressed_then_encrypted.gpg
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Output:</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># -rw-r--r--@ 1 kevin  staff   5.9M Dec 12 17:13 log.txt</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># -rw-r--r--@ 1 kevin  staff    15K Dec 12 18:15 compressed_first.gz</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># -rw-r--r--@ 1 kevin  staff    15K Dec 12 18:15 compressed_then_encrypted.gpg</span>
</span></span></code></pre></div><p><strong>Why it matters</strong>
Now that&rsquo;s what we expected to see. The <code>compressed_then_encrypted.gpg</code> file is tiny (15 KB!). We have the same data, but it&rsquo;s small <em>and</em> secret.</p>
<p><strong>Compress raw data first to capture patterns, and encryption preserves the smaller size.</strong></p>
<h4 id="final-results">Final Results</h4>
<p>Let&rsquo;s put the two final files side-by-side for a dramatic comparison.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ls -lh encrypted_then_compressed.gz compressed_then_encrypted.gpg
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Output:</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># -rw-r--r--@ 1 kevin  staff    15K Dec 12 18:15 compressed_then_encrypted.gpg</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># -rw-r--r--@ 1 kevin  staff   5.9M Dec 12 18:14 encrypted_then_compressed.gz</span>
</span></span></code></pre></div><img src="/d2-diagrams/78269656e6ab4257f6280e9a6ace2401c2161e4e67c98990e547d263ecbbe17b.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /><p>Getting the order right saved us over 99% of the disk space.</p>
<p><em>A quick note on the results:</em> The dummy <code>log.txt</code> file we generated is extremely repetitive, which makes for a dramatic demonstration. Real-world data won&rsquo;t always compress this well, but the principle holds: you&rsquo;ll always get better results by compressing the patterned data <em>before</em> you encrypt it.</p>
<h3 id="security-implications">Security Implications</h3>
<p>Hopefully, you&rsquo;re convinced that &ldquo;Compress then Encrypt&rdquo; is the rule to use in all cases, right? Almost. There&rsquo;s one major exception where this exact sequence can create a subtle but powerful security hole.</p>
<p>When an attacker can influence data that gets compressed and then encrypted, they can create a &ldquo;compression oracle&rdquo; to slowly guess secret information. Compression leaks information because matching patterns in the data cause it to shrink more, so the final size becomes a side-channel that reveals details about the original content. This forms the basis of two infamous attacks: CRIME and BREACH.</p>
<h4 id="crime-the-attack-that-killed-tls-compression">CRIME: The Attack That Killed TLS Compression</h4>
<p>The <strong>CRIME</strong> attack went after compression that was happening directly at the TLS layer, the &lsquo;S&rsquo; in HTTPS.</p>
<ol>
<li>
<p><strong>The Attack:</strong> CRIME allowed an attacker to steal secret authentication cookies. They&rsquo;d trick a victim&rsquo;s browser into sending requests containing their cookie, and they would inject their own guesses for the cookie&rsquo;s value into the request. By watching the size of the final encrypted response from the server, they could see when their guess matched a character in the real cookie, because the compressed size would shrink just a tiny bit. They could repeat this to uncover the secret, one character at a time.</p>
</li>
<li>
<p><strong>The Impact:</strong> The consequences were immediate and severe. Everyone realized that letting the TLS protocol itself handle compression was a footgun waiting to go off. It was quickly disabled in all major browsers and is now <strong>forbidden in the TLS 1.3 specification.</strong> It&rsquo;s gone for good.</p>
</li>
</ol>
<h4 id="breach-the-attack-that-wont-go-away">BREACH: The Attack That Won&rsquo;t Go Away</h4>
<p>BREACH is CRIME&rsquo;s younger, more persistent sibling. It targets compression at the HTTP level (the <code>gzip</code> or <code>brotli</code> your web server uses), not the TLS layer.</p>
<ol>
<li>
<p><strong>The Attack:</strong> The idea is the same. An attacker tricks a browser into sending repeated requests, but this time they are trying to guess a secret embedded in the HTML of the response body, like a hidden CSRF token. If the response contains both the secret and some input from the attacker (like a reflected search term), they can once again watch the compressed size to build a compression oracle.</p>
<p>BREACH only works when the response includes <strong>both</strong> attacker-controlled input and a secret <em>in the same compressed payload</em>. If your application never mixes these, the attack isn&rsquo;t possible.</p>
</li>
<li>
<p><strong>The Impact:</strong> We can&rsquo;t just turn off HTTP compression, as the performance hit to the web would be catastrophic. So, stopping BREACH falls on application developers, with a few key strategies:</p>
<p>Using <code>SameSite=Strict</code> on your session cookies is a strong defense as it prevents the browser from sending them on any cross-site requests, blocking the main attack vector. While many applications must use <code>SameSite=Lax</code> for practical navigation, it also significantly reduces the risk by withholding cookies on cross-origin subrequests, preventing the most common BREACH scenarios.</p>
<p>The best defense is to never mix secrets and user-controllable data in the same compressed response.</p>
<p>Obscure the true length by adding a random number of bytes to the response, making the compression ratio useless to an attacker.</p>
<p>Use rate-limiting to slow down attackers so the thousands of requests needed for the attack become impractical.</p>
<p>At this point, you might be thinking: &ldquo;Wait, HTTP/2 and HTTP/3 use header compression (HPACK and QPACK). If my secrets are in headers, doesn&rsquo;t that bring back the BREACH risk?&rdquo; <em>That is a concern here</em>. Headers like <code>Authorization</code>, <code>Cookie</code>, <code>Set-Cookie</code>, or <code>X-Auth-Token</code> often contain high-value secrets and are prime targets for a compression oracle. To handle this, HTTP/2 and HTTP/3 protocols include a specific &ldquo;sensitive&rdquo; flag. When a header is marked this way, the compressor is told to treat the value as a literal rather than adding it to the dynamic compression table. This prevents the value from being used as a reference point for future matches and prevents the side-channel without forcing us to disable compression for the entire connection.</p>
</li>
</ol>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/encryption-vs-compression/oracle_hu_546e1b5c528dd343.webp" class="center"/>
    


<h2 id="final-thoughts">Final thoughts</h2>
<p>What started as an interesting little interview question lead us to discussing real security concerns with mixing compression and encryption.</p>
<p>In most cases, the rule is unequivocal: <strong>compress first, then encrypt</strong>. Whether you are archiving logs or moving backups, compressing patterned data before it becomes random noise saves significant time and money.</p>
<p>The only real exception is when you are handling interactive traffic. If an attacker can control part of a message that contains a secret, the compressed size actually leaks information. For static archives, this isn&rsquo;t an issue. But for live web protocols, you either have to use modern safeguards like sensitive header flags or disable compression entirely for those specific payloads.</p>
<h3 id="further-reading">Further Reading</h3>
<p><strong>The Specifications</strong></p>
<ul>
<li><strong>RFC 1952 (GZIP):</strong> Specification for GZIP compression, used in examples above.
<a href="https://datatracker.ietf.org/doc/html/rfc1952" rel="external">https://datatracker.ietf.org/doc/html/rfc1952</a></li>
<li><strong>RFC 8446 (TLS 1.3):</strong> Note section 1.2, which explicitly removes support for compression to mitigate the attacks mentioned above.
<a href="https://datatracker.ietf.org/doc/html/rfc8446" rel="external">https://datatracker.ietf.org/doc/html/rfc8446</a></li>
<li><strong>RFC 4880 (OpenPGP):</strong> The technical specification for the OpenPGP Message Format, used by GPG.
<a href="https://datatracker.ietf.org/doc/html/rfc4880" rel="external">https://datatracker.ietf.org/doc/html/rfc4880</a></li>
</ul>
<p><strong>The Security Vulnerabilities</strong></p>
<ul>
<li><strong>The CRIME Attack:</strong> A demo of how compression can leak secrets at the SSL/TLS layer.
<a href="https://en.wikipedia.org/wiki/CRIME" rel="external">https://en.wikipedia.org/wiki/CRIME</a></li>
<li><strong>The BREACH Attack:</strong> A similar side-channel attack targeting HTTP-level compression.
<a href="http://breachattack.com/" rel="external">http://breachattack.com/</a></li>
</ul>
<p><strong>The Theory</strong></p>
<ul>
<li><strong>Shannon Entropy:</strong> The mathematical concept explaining why encrypted data (high entropy) cannot be compressed.
<a href="https://en.wikipedia.org/wiki/Entropy_%28information_theory%29" rel="external">https://en.wikipedia.org/wiki/Entropy_(information_theory)</a></li>
<li><strong>Why can&rsquo;t I compress an encrypted file?</strong> This Math StackExchange question/answer asks and answers why encrypted data is difficult to compress.
<a href="https://math.stackexchange.com/questions/4858088/why-cant-i-compress-an-encrypted-file" rel="external">https://math.stackexchange.com/questions/4858088/why-cant-i-compress-an-encrypted-file</a></li>
</ul>
]]></content:encoded></item><item><title>On Creating My Own Cover Art</title><link>https://kmcd.dev/posts/cover-art/</link><pubDate>Mon, 15 Dec 2025 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/cover-art/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/cover-art/cover.svg" /> &lt;/p>
                
                Why I felt guilty using AI art, and chose to embrace a more personal, code-driven creative process.
                </description><content:encoded><![CDATA[<p>I felt guilty using AI-generated art for my blog.</p>
<p>When AI tools first appeared, they felt like magic. A simple text prompt could conjure a unique, often beautiful image to accompany my writing. Sometimes it would even have the correct number of fingers. Amazing! It felt like a powerful new way to communicate, especially for me, who is not artistically inclined. Over time, however, the process felt hollow. Beyond the feeling of it being a shortcut, I found that AI art often fell short of capturing the vision I had in mind for my cover art. It felt like the cover art was the result of me complaining at a machine until it produced something that was kind-of what I wanted and had the fewest obvious flaws.</p>
<p>I was doing this as a shortcut but it began to feel like a chore. So starting with my previous post, I&rsquo;m changing how the covers are generated for my posts.</p>
<p>I&rsquo;ve always loved generative art: art created from code, algorithms, and a touch of randomness. It just felt right to spend time generating my own cover art with code, rather than asking an AI. For a blog about making things with code, it just makes sense that code would generate the cover art as well.</p>
<p>So, I built a <a href="https://github.com/sudorandom/kmcd.dev/blob/main/cmd/cover-art-generator/main.go" rel="external">small Go program</a> that generates a raster image, and then feeds it into a Go tool/library called <a href="https://github.com/fogleman/primitive" rel="external"><code>primitive</code></a> which reinterprets it into a stylized vector piece.</p>
<p>The Go script generates random shapes of different sizes, pulling from a few color palettes that I created. It also randomly draws lines connecting the shapes together, because I like the &ldquo;graph diagram&rdquo; feel that it gives. For example, here&rsquo;s the little chunk of code that draws a star. For drawing the initial shapes, I decided to use a library called <a href="https://github.com/fogleman/gg" rel="external">Go Graphics (github.com/fogleman/gg)</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">drawStar</span><span style="color:#eceff4">(</span>dc <span style="color:#81a1c1">*</span>gg<span style="color:#eceff4">.</span>Context<span style="color:#eceff4">,</span> points<span style="color:#eceff4">,</span> centerX<span style="color:#eceff4">,</span> centerY<span style="color:#eceff4">,</span> outerRadius <span style="color:#81a1c1">float64</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	innerRadius <span style="color:#81a1c1">:=</span> outerRadius <span style="color:#81a1c1">*</span> <span style="color:#b48ead">0.4</span>
</span></span><span style="display:flex;"><span>	dc<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewSubPath</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> i <span style="color:#81a1c1">:=</span> <span style="color:#b48ead">0.0</span><span style="color:#eceff4">;</span> i <span style="color:#eceff4">&lt;</span> points<span style="color:#81a1c1">*</span><span style="color:#b48ead">2</span><span style="color:#eceff4">;</span> i<span style="color:#81a1c1">++</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		r <span style="color:#81a1c1">:=</span> outerRadius
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">(</span>i<span style="color:#eceff4">)</span><span style="color:#81a1c1">%</span><span style="color:#b48ead">2</span> <span style="color:#81a1c1">!=</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			r <span style="color:#eceff4">=</span> innerRadius
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		angle <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">(</span>math<span style="color:#eceff4">.</span>Pi<span style="color:#81a1c1">*</span><span style="color:#b48ead">2</span><span style="color:#81a1c1">/</span><span style="color:#eceff4">(</span>points<span style="color:#81a1c1">*</span><span style="color:#b48ead">2</span><span style="color:#eceff4">))</span><span style="color:#81a1c1">*</span>i <span style="color:#81a1c1">-</span> math<span style="color:#eceff4">.</span>Pi<span style="color:#81a1c1">/</span><span style="color:#b48ead">2</span>
</span></span><span style="display:flex;"><span>		x <span style="color:#81a1c1">:=</span> centerX <span style="color:#81a1c1">+</span> r<span style="color:#81a1c1">*</span>math<span style="color:#eceff4">.</span><span style="color:#88c0d0">Cos</span><span style="color:#eceff4">(</span>angle<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		y <span style="color:#81a1c1">:=</span> centerY <span style="color:#81a1c1">+</span> r<span style="color:#81a1c1">*</span>math<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sin</span><span style="color:#eceff4">(</span>angle<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		dc<span style="color:#eceff4">.</span><span style="color:#88c0d0">LineTo</span><span style="color:#eceff4">(</span>x<span style="color:#eceff4">,</span> y<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	dc<span style="color:#eceff4">.</span><span style="color:#88c0d0">ClosePath</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>The output is a chaotic but structured smattering of shapes and lines.</p>
<p>To transform this into something with more visual interest, I turned to <code>primitive</code>. This library attempts to recreate the input image using a limited number of geometric shapes. It smooths out the chaos, turning the harsh lines into a stylized, abstract vector piece.</p>











    
        
    



    
        
    




    




    


<figure>
    <div style="display: flex; gap: 10px;">
        <div style="flex: 1;">
            <p><strong>Before:</strong></p>
            <img src="https://kmcd.dev/posts/cover-art/process-before_hu_da20afb1574203ed.png" alt="Before" style="max-width: 100%; height: auto;">
        </div>
        <div style="flex: 1;">
            <p><strong>After:</strong></p>
            <img src="https://kmcd.dev/posts/cover-art/process-after.svg" alt="After" style="max-width: 100%; height: auto;">
        </div>
    </div>
    
    <figcaption>Before and after applying the <code>primitive</code> transformation.</figcaption>
    
</figure>

<p>It&rsquo;s also worth noting that the resulting images from <code>primitive</code> are SVGs. Because they&rsquo;re vector-based, they are often much smaller than their raster counterparts (PNG, JPG) and scale perfectly to any size. This means they look crisp on everything from a mobile phone to a 4K monitor without increasing the file size.</p>
<p>This new process feels right. I&rsquo;m pretty happy with how this has turned out. It&rsquo;s more personal, and there&rsquo;s a fun element of surprise in seeing what the code comes up with each time. If I ever get bored of the results, I have the ability to change the code or some of the settings.</p>
<p>Here&rsquo;s a gallery of the art this process has helped create. Note that not all of these images look amazing. When creating new articles, I plan on generating images with this script until I find one that I like well enough to use for the cover.</p>







<div id="gallery-f54477ee" class="slideshow-container">
  <style>
  #gallery-f54477ee {
    position: relative;
    margin: auto;
    max-width: 700px;
    aspect-ratio: 1200 / 630;
    background-color: #f0f0f0;
    overflow: hidden;
  }

  #gallery-f54477ee .mySlides {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    transition: opacity 0.4s ease-in-out;
  }

  #gallery-f54477ee .mySlides.slide-active {
    opacity: 1;
  }

  #gallery-f54477ee .mySlides figure {
    margin: 0;
    width: 100%;
    height: 100%;
  }

  #gallery-f54477ee .mySlides a {
    display: block;
    width: 100%;
    height: 100%;
  }

  #gallery-f54477ee .mySlides img {
    width: 100%;
    height: 100%;
    object-fit: contain;
  }

  #gallery-f54477ee .prev, #gallery-f54477ee .next {
    cursor: pointer;
    position: absolute;
    top: 50%;
    width: auto;
    transform: translateY(-50%);
    padding: 16px;
    color: white;
    font-weight: bold;
    font-size: 18px;
    transition: 0.3s ease;
    border-radius: 0 3px 3px 0;
    user-select: none;
    background-color: rgba(0,0,0,0.2);
    border: none;
    z-index: 10;
  }

  #gallery-f54477ee .next {
    right: 0;
    border-radius: 3px 0 0 3px;
  }
  
  #gallery-f54477ee .prev:hover, #gallery-f54477ee .next:hover {
    background-color: rgba(0,0,0,0.8);
  }
  </style>

  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-01.svg">
          <img src="/posts/cover-art/example-01.svg" loading="lazy" alt="example-01.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-02.svg">
          <img src="/posts/cover-art/example-02.svg" loading="lazy" alt="example-02.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-03.svg">
          <img src="/posts/cover-art/example-03.svg" loading="lazy" alt="example-03.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-04.svg">
          <img src="/posts/cover-art/example-04.svg" loading="lazy" alt="example-04.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-05.svg">
          <img src="/posts/cover-art/example-05.svg" loading="lazy" alt="example-05.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-06.svg">
          <img src="/posts/cover-art/example-06.svg" loading="lazy" alt="example-06.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-07.svg">
          <img src="/posts/cover-art/example-07.svg" loading="lazy" alt="example-07.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-08.svg">
          <img src="/posts/cover-art/example-08.svg" loading="lazy" alt="example-08.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-09.svg">
          <img src="/posts/cover-art/example-09.svg" loading="lazy" alt="example-09.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-10.svg">
          <img src="/posts/cover-art/example-10.svg" loading="lazy" alt="example-10.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-11.svg">
          <img src="/posts/cover-art/example-11.svg" loading="lazy" alt="example-11.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-12.svg">
          <img src="/posts/cover-art/example-12.svg" loading="lazy" alt="example-12.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-13.svg">
          <img src="/posts/cover-art/example-13.svg" loading="lazy" alt="example-13.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-14.svg">
          <img src="/posts/cover-art/example-14.svg" loading="lazy" alt="example-14.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-15.svg">
          <img src="/posts/cover-art/example-15.svg" loading="lazy" alt="example-15.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-16.svg">
          <img src="/posts/cover-art/example-16.svg" loading="lazy" alt="example-16.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-17.svg">
          <img src="/posts/cover-art/example-17.svg" loading="lazy" alt="example-17.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-18.svg">
          <img src="/posts/cover-art/example-18.svg" loading="lazy" alt="example-18.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-19.svg">
          <img src="/posts/cover-art/example-19.svg" loading="lazy" alt="example-19.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-20.svg">
          <img src="/posts/cover-art/example-20.svg" loading="lazy" alt="example-20.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-21.svg">
          <img src="/posts/cover-art/example-21.svg" loading="lazy" alt="example-21.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-22.svg">
          <img src="/posts/cover-art/example-22.svg" loading="lazy" alt="example-22.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-23.svg">
          <img src="/posts/cover-art/example-23.svg" loading="lazy" alt="example-23.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-24.svg">
          <img src="/posts/cover-art/example-24.svg" loading="lazy" alt="example-24.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-25.svg">
          <img src="/posts/cover-art/example-25.svg" loading="lazy" alt="example-25.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-26.svg">
          <img src="/posts/cover-art/example-26.svg" loading="lazy" alt="example-26.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-27.svg">
          <img src="/posts/cover-art/example-27.svg" loading="lazy" alt="example-27.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-28.svg">
          <img src="/posts/cover-art/example-28.svg" loading="lazy" alt="example-28.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-29.svg">
          <img src="/posts/cover-art/example-29.svg" loading="lazy" alt="example-29.svg" />
      </a>
  </figure>
  </div>
  
  <div class="mySlides">
    <figure>
      <a href="https://kmcd.dev/posts/cover-art/example-30.svg">
          <img src="/posts/cover-art/example-30.svg" loading="lazy" alt="example-30.svg" />
      </a>
  </figure>
  </div>
  

  <button class="prev">&#10094;</button>
  <button class="next">&#10095;</button>
</div>

<noscript>
  <style>
    #gallery-f54477ee .mySlides {
      position: static;
      opacity: 1;
      height: auto;
      margin-bottom: 1rem;
    }
    #gallery-f54477ee .prev, #gallery-f54477ee .next {
      display: none;
    }
    #gallery-f54477ee {
      aspect-ratio: auto;
      background-color: transparent;
    }
    #gallery-f54477ee .mySlides img {
      position: static;
      height: auto;
      object-fit: initial;
    }
  </style>
</noscript>

<script>
(function() {
  const gallery = document.getElementById('gallery-f54477ee');
  if (!gallery) return;

  const slides = Array.from(gallery.getElementsByClassName("mySlides"));
  const prevButton = gallery.getElementsByClassName("prev")[0];
  const nextButton = gallery.getElementsByClassName("next")[0];
  
  if (slides.length === 0) return;

  let slideIndex = 1;
  let slideTimeout;

  function plusSlides(n) {
    clearTimeout(slideTimeout);
    showSlides(slideIndex += n);
  }

  function showSlides(n) {
    if (n > slides.length) { slideIndex = 1; }
    if (n < 1) { slideIndex = slides.length; }

    for (let i = 0; i < slides.length; i++) {
      slides[i].classList.remove('slide-active');
      slides[i].style.pointerEvents = 'none';
    }

    if (slides.length > 0) {
        slides[slideIndex - 1].classList.add('slide-active');
        slides[slideIndex - 1].style.pointerEvents = 'auto';
    }

    slideTimeout = setTimeout(() => {
      plusSlides(1);
    }, 4000); 
  }

  if (prevButton) {
    prevButton.addEventListener('click', () => plusSlides(-1));
  }
  if (nextButton) {
    nextButton.addEventListener('click', () => plusSlides(1));
  }
  
  showSlides(slideIndex);

  const images = [
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-01.svg",
      title: "example-01.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-02.svg",
      title: "example-02.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-03.svg",
      title: "example-03.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-04.svg",
      title: "example-04.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-05.svg",
      title: "example-05.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-06.svg",
      title: "example-06.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-07.svg",
      title: "example-07.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-08.svg",
      title: "example-08.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-09.svg",
      title: "example-09.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-10.svg",
      title: "example-10.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-11.svg",
      title: "example-11.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-12.svg",
      title: "example-12.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-13.svg",
      title: "example-13.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-14.svg",
      title: "example-14.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-15.svg",
      title: "example-15.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-16.svg",
      title: "example-16.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-17.svg",
      title: "example-17.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-18.svg",
      title: "example-18.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-19.svg",
      title: "example-19.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-20.svg",
      title: "example-20.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-21.svg",
      title: "example-21.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-22.svg",
      title: "example-22.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-23.svg",
      title: "example-23.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-24.svg",
      title: "example-24.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-25.svg",
      title: "example-25.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-26.svg",
      title: "example-26.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-27.svg",
      title: "example-27.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-28.svg",
      title: "example-28.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-29.svg",
      title: "example-29.svg"
    },
    
    {
      src: "https:\/\/kmcd.dev\/posts\/cover-art\/example-30.svg",
      title: "example-30.svg"
    },
    
  ];

  gallery.addEventListener('click', (e) => {
    const clickedSlide = e.target.closest('.mySlides');
    if (!clickedSlide) return;

    const slideIndex = slides.findIndex(slide => slide === clickedSlide);

    if (slideIndex > -1) {
      e.preventDefault();
      Spotlight.show(images, {
        index: slideIndex + 1
      });
    }
  });

  gallery.addEventListener('spotlight:change', ({detail}) => {
    slideIndex = detail.index;
    showSlides(slideIndex);
  });
})();
</script>
<p><a href="https://github.com/sudorandom/kmcd.dev/blob/main/cmd/cover-art-generator/main.go" rel="external">Click here for the full source code</a>.</p>
]]></content:encoded></item><item><title>Traceroute Tool from Scratch in Go</title><link>https://kmcd.dev/posts/traceroute/</link><pubDate>Tue, 09 Dec 2025 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/traceroute/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/traceroute/cover.svg" /> &lt;/p>
                
                Map your route through the Internet
                </description><content:encoded><![CDATA[<p>Traceroute is a network diagnostic utility that maps the path that packets take across an IP network. It provides a list of the intermediate routers a packet traverses on its way to a final destination, along with the time taken for each &ldquo;hop.&rdquo; This information is crucial for diagnosing network latency and identifying points of failure. Personally, I think it is super cool that there&rsquo;s a way to figure out the route that your packets are taking.</p>
<p>In this article, I&rsquo;ll dig into how traceroute works and show you how to build a simple version from scratch using Go.</p>
<h2 id="icmp-the-internets-control-protocol">ICMP: The Internet&rsquo;s Control Protocol</h2>
<p>Before diving into traceroute, we need to understand the protocol that powers it: <strong>ICMP (Internet Control Message Protocol)</strong>. ICMP is a network-layer protocol used by network devices to send error messages and operational information. It&rsquo;s <em>not</em> for exchanging data, but for diagnostics, control, and error reporting. In fact, many routers and devices will heavily throttle ICMP traffic to protect their CPU usage, since generating these error messages is computationally expensive compared to simply forwarding packets.</p>
<h3 id="the-classic-use-case-ping">The Classic Use Case: <code>ping</code></h3>
<p>The most common and well-known use of ICMP is the <code>ping</code> utility. <code>ping</code> is used to test the reachability of a host on an IP network. It works by sending an <strong><code>ICMP Echo Request</code></strong> to the target host. If the host is reachable, it responds with an <strong><code>ICMP Echo Reply</code></strong>.</p>
<p>Here is what a typical <code>ping</code> to <code>google.com</code> looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ ping google.com
</span></span><span style="display:flex;"><span>PING google.com <span style="color:#81a1c1">(</span>192.0.0.88<span style="color:#81a1c1">)</span>: <span style="color:#b48ead">56</span> data bytes
</span></span><span style="display:flex;"><span><span style="color:#b48ead">64</span> bytes from 192.0.0.88: icmp_seq<span style="color:#81a1c1">=</span><span style="color:#b48ead">0</span> ttl<span style="color:#81a1c1">=</span><span style="color:#b48ead">64</span> time<span style="color:#81a1c1">=</span>88.471 ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">64</span> bytes from 192.0.0.88: icmp_seq<span style="color:#81a1c1">=</span><span style="color:#b48ead">1</span> ttl<span style="color:#81a1c1">=</span><span style="color:#b48ead">64</span> time<span style="color:#81a1c1">=</span>88.708 ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">64</span> bytes from 192.0.0.88: icmp_seq<span style="color:#81a1c1">=</span><span style="color:#b48ead">2</span> ttl<span style="color:#81a1c1">=</span><span style="color:#b48ead">64</span> time<span style="color:#81a1c1">=</span>88.535 ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">64</span> bytes from 192.0.0.88: icmp_seq<span style="color:#81a1c1">=</span><span style="color:#b48ead">3</span> ttl<span style="color:#81a1c1">=</span><span style="color:#b48ead">64</span> time<span style="color:#81a1c1">=</span>88.579 ms
</span></span><span style="display:flex;"><span>^C
</span></span><span style="display:flex;"><span>--- google.com ping statistics ---
</span></span><span style="display:flex;"><span><span style="color:#b48ead">4</span> packets transmitted, <span style="color:#b48ead">4</span> packets received, 0.0% packet loss
</span></span><span style="display:flex;"><span>round-trip min/avg/max/stddev <span style="color:#81a1c1">=</span> 88.471/88.573/88.708/0.087 ms
</span></span></code></pre></div><p>This tells us that <code>google.com</code> is reachable and provides the round-trip time for each packet.</p>
<h3 id="from-ping-to-traceroute">From <code>ping</code> to <code>traceroute</code></h3>
<p>While <code>ping</code> tells you <em>if</em> you can reach a destination, <code>traceroute</code> tells you <em>how</em> you get there. It builds on the same ICMP foundation but uses a different ICMP message in a clever way. Instead of just checking for the final &ldquo;Echo Reply,&rdquo; it intentionally triggers <strong><code>ICMP Time Exceeded</code></strong> messages from intermediate routers to map out the path. This is all done by manipulating the Time-To-Live (TTL) field in the IP packet header.</p>
<h3 id="ip-and-icmp-packet-formats">IP and ICMP Packet Formats</h3>
<p>To fully understand how <code>traceroute</code> works, we should understand the basic structure of IP and ICMP packets.</p>
<h4 id="ipv4-packet-format">IPv4 Packet Format</h4>
<p>All network traffic on the internet is encapsulated within IP packets. The IP header contains crucial information for routing, including source and destination IP addresses, and, most importantly for <code>traceroute</code>, the Time-To-Live (TTL) field.</p>
<div class="container">
  <pre class="mermaid">packet
0-3: "Version"
4-7: "IHL"
8-15: "Type of Service (TOS)"
16-31: "Total Length"
32-47: "Identification"
48-50: "Flags"
51-63: "Fragment Offset"
64-71: "TTL"
72-79: "Protocol"
80-95: "Header Checksum"
96-127: "Source IP Address"
128-159: "Destination IP Address"
160-191: "Options & Padding"
192-223: "Data (Variable Length)"
  </pre>
</div>
<p>Mostly, I want you to note the Time-To-Live (TTL) field, which is central to how traceroute operates. We&rsquo;ll touch more on TTLs a little bit later. But first, let&rsquo;s look at the structure of ICMP packets.</p>
<h4 id="icmp-packet-format">ICMP Packet Format</h4>
<p>ICMP messages, including the Echo Request/Reply used by <code>ping</code> and the Time Exceeded messages used by <code>traceroute</code>, are themselves encapsulated within an IP packet&rsquo;s payload. The ICMP header specifies the message type (e.g., Echo Request, Echo Reply, Time Exceeded) and a code, along with a checksum for integrity. For Echo messages, it also includes an identifier and sequence number.</p>
<div class="container">
  <pre class="mermaid">packet
0-7: "Type (e.g., 8=Request, 0=Reply)"
8-15: "Code (e.g., 0)"
16-31: "Checksum"
32-47: "Identifier (BE) / (Varies by Type)"
48-63: "Sequence Number (BE) / (Varies by Type)"
64-95: "Data Payload (variable length)"
  </pre>
</div>
<p>Crucially, since ICMP messages are encapsulated within IP packets, it is the <em>enclosing IP packet</em> that carries the TTL field and other IP header information, not the ICMP message itself. The ICMP header defines the specific control message, while the IP header handles its transport.</p>
<h2 id="how-does-traceroute-work">How does traceroute work?</h2>
<p>Unlike a direct message, data sent over the Internet hops through a chain of routers to reach its destination. Traceroute reveals this path by exploiting a fail-safe mechanism called <strong>Time-To-Live (TTL)</strong>.</p>
<p>Unlike what the name suggests, TTL does not represent actual time. It represents the number of hops before routers are instructed to give up on the packet. It is kind-of like a self-destruct counter. Every time a packet passes through a router (a &ldquo;hop&rdquo;), this counter decreases by 1. When it hits 0, the router discards the packet and sends an error message back to you.</p>
<p>Traceroute uses this mechanism to map the network:</p>
<ol>
<li><strong>Hop 1</strong>: It sends a packet with a TTL of 1. The very first router receives it, decreases the TTL to 0, drops the packet, and replies with &ldquo;Time Exceeded.&rdquo; We have now identified the first router.</li>
<li><strong>Hop 2</strong>: It sends a new packet with a TTL of 2. The first router passes it along (TTL becomes 1). The second router receives it, decreases TTL to 0, drops it, and replies &ldquo;Time Exceeded.&rdquo; We have identified the second router.</li>
<li><strong>Repeat</strong>: It continues increasing the TTL by 1 until the packet finally reaches the destination server and receives a standard reply.</li>
</ol>
<h3 id="probe-methods-icmp-udp-and-tcp">Probe Methods: ICMP, UDP, and TCP</h3>
<p>While our examples use ICMP Echo Requests, it&rsquo;s important to understand that the TTL expiration mechanism is protocol-agnostic. The TTL field is in the IP header, which encapsulates TCP, UDP, and ICMP packets alike. Because of this, <code>traceroute</code> utilities can use different protocols for their &ldquo;probes.&rdquo;</p>
<ul>
<li>
<p><strong>UDP Probes:</strong> This is the traditional method used by Linux and macOS. <code>traceroute</code> sends UDP datagrams to an invalid, high-numbered port at the destination. For intermediate hops, the process is identical: routers send <code>ICMP Time Exceeded</code> messages when the TTL expires. When the UDP packet finally reaches the destination, the host&rsquo;s OS sees that no service is listening on that port and sends back an <code>ICMP Port Unreachable</code> error. This &ldquo;unreachable&rdquo; message ironically signals a successful arrival at the destination.</p>
</li>
<li>
<p><strong>TCP Probes:</strong> Some <code>traceroute</code> versions can send TCP SYN packets, similar to how a TCP connection is initiated. Intermediate routers still respond with <code>ICMP Time Exceeded</code>. If the packet reaches the destination, the host will respond with either a <code>TCP SYN/ACK</code> (if the port is open) or a <code>TCP RST</code> (if the port is closed). Either response confirms that the destination has been reached.</p>
</li>
<li>
<p><strong>ICMP Probes:</strong> This method leverages <code>ICMP Echo Requests</code>, similar to the ubiquitous <code>ping</code> command found on Linux (and other OSes). While Linux <code>traceroute</code> traditionally uses UDP, Windows&rsquo; <code>tracert</code> utility uses ICMP Echo Requests by default, as does our Go example. It listens for <code>ICMP Time Exceeded</code> from routers and a final <code>ICMP Echo Reply</code> from the destination.</p>
</li>
</ul>
<p>Regardless of the protocol used for the probe, the fundamental mechanic remains the same: an <code>ICMP Time Exceeded</code> message is always generated by routers when the TTL reaches zero.</p>
<h2 id="what-layer-does-traceroute-operate-on">What layer does traceroute operate on?</h2>
<p>Traceroute operates at <strong>Layer 3 (the Network Layer)</strong> of the OSI model. This is because its core components are all Layer 3 protocols and mechanisms:</p>
<ul>
<li><strong>IP (Internet Protocol):</strong> Traceroute is fundamentally about sending and routing IP packets.</li>
<li><strong>ICMP (Internet Control Message Protocol):</strong> The diagnostic messages, such as <code>Time Exceeded</code> and <code>Echo Reply</code>, are ICMP messages. ICMP is a network-layer protocol that operates alongside IP.</li>
<li><strong>TTL (Time To Live):</strong> The TTL field is part of the IP header itself. Routers, which are Layer 3 devices, are responsible for decrementing this value.</li>
</ul>
<p>Here&rsquo;s a diagram of the OSI model, showing where traceroute fits in:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Layer.</th>
          <th style="text-align: left">Note</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">7: Application</td>
          <td style="text-align: left"></td>
      </tr>
      <tr>
          <td style="text-align: left">6: Presentation</td>
          <td style="text-align: left"></td>
      </tr>
      <tr>
          <td style="text-align: left">5: Session</td>
          <td style="text-align: left"></td>
      </tr>
      <tr>
          <td style="text-align: left">4: Transport</td>
          <td style="text-align: left"></td>
      </tr>
      <tr>
          <td style="text-align: left">3: <strong>Network (IP)</strong></td>
          <td style="text-align: left">← You are here.</td>
      </tr>
      <tr>
          <td style="text-align: left">2: Data Link</td>
          <td style="text-align: left"></td>
      </tr>
      <tr>
          <td style="text-align: left">1: Physical</td>
          <td style="text-align: left"></td>
      </tr>
  </tbody>
</table>
<p>While the core TTL mechanism and ICMP <code>Time Exceeded</code> messages clearly place traceroute&rsquo;s fundamental operation at Layer 3, it&rsquo;s important to note that the probe packets themselves can originate from Layer 4 protocols. As discussed in the &ldquo;Probe Methods&rdquo; section, <code>traceroute</code> often utilizes UDP or TCP packets. In these cases, the probe <em>originates</em> at Layer 4 (Transport Layer) but relies on the Layer 3 IP header&rsquo;s TTL for its diagnostic function. This makes <code>traceroute</code> a tool that crosses over a couple OSI layer boundaries, sending probes with Layer 3 or Layer 4 and utilizing ICMP&rsquo;s time exceeded errors from Layer 3.</p>
<h4 id="ttl-exhaustion">TTL Exhaustion</h4>
<p>When a packet&rsquo;s TTL is set to 1, its journey is cut short at the first router. The router discards the packet and sends an <code>ICMP Time Exceeded</code> message back. This is the fundamental mechanism for discovering a hop.</p>
<img src="/d2-diagrams/392bd16aabdcfcf316aaad06b9a4f25fbeeef7beaef03efe03dcb154da78e99b.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /><h4 id="symmetric-packet-flow-to-destination">Symmetric Packet Flow to Destination</h4>
<p>In an ideal scenario, a packet reaches the final server, and the <code>ICMP Echo Reply</code> travels back to the client along the exact same path.</p>
<img src="/d2-diagrams/76070ba72d408fdce77405a313f46502fc5a9d86d247be97f6b47b24845bc44b.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /><h4 id="asymmetric-routing-the-reality-of-the-internet">Asymmetric Routing (The Reality of the Internet)</h4>
<p>It is important to note that the return path is <strong>not guaranteed</strong> to be the same as the request path. The Internet is a mesh of dynamic networks, and routing decisions are made independently in each direction. The diagram below shows a more realistic scenario where the return path uses a different set of routers.</p>
<img src="/d2-diagrams/45fb688a00c71961af8b879f556aaa2beaf76ad839ea8dcc3c035c6be6b82ff2.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /><p>For dealing with the complexities of inconsistent and asymmetric routes, tools like <a href="https://www.bitwizard.nl/mtr/" rel="external">mtr</a> (My Traceroute) continuously repeat traceroutes, providing real-time statistics and a clearer picture of network performance over time.</p>
<h2 id="building-a-traceroute-in-go">Building a Traceroute in Go</h2>
<p>We can build a functional traceroute tool in Go using the <code>golang.org/x/net/icmp</code> package, which provides access to the low-level networking primitives required. The process involves setting up a &ldquo;listener&rdquo; to receive ICMP messages, then looping to send probes with an incrementally higher TTL.</p>
<h3 id="step-1-crafting-and-sending-the-icmp-probe">Step 1: Crafting and Sending the ICMP Probe</h3>
<p>First, we need to construct an <code>ICMP Echo Request</code>. The <code>ID</code> field should be unique to our process to distinguish replies intended for us from other network traffic, and we use the <code>Seq</code> (sequence) number to track which hop this probe corresponds to. After crafting the message, we set the TTL for this specific packet and send it on its way.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Set the TTL for our outgoing packet</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> c<span style="color:#eceff4">.</span><span style="color:#88c0d0">IPv4PacketConn</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">SetTTL</span><span style="color:#eceff4">(</span>ttl<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;SetTTL failed: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Create an ICMP Echo Message</span>
</span></span><span style="display:flex;"><span>m <span style="color:#81a1c1">:=</span> icmp<span style="color:#eceff4">.</span>Message<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    Type<span style="color:#eceff4">:</span> ipv4<span style="color:#eceff4">.</span>ICMPTypeEcho<span style="color:#eceff4">,</span> Code<span style="color:#eceff4">:</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    Body<span style="color:#eceff4">:</span> <span style="color:#81a1c1">&amp;</span>icmp<span style="color:#eceff4">.</span>Echo<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        ID<span style="color:#eceff4">:</span>   os<span style="color:#eceff4">.</span><span style="color:#88c0d0">Getpid</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">&amp;</span> <span style="color:#b48ead">0xffff</span><span style="color:#eceff4">,</span> <span style="color:#616e87;font-style:italic">// Use process ID to uniquely identify this traceroute</span>
</span></span><span style="display:flex;"><span>        Seq<span style="color:#eceff4">:</span>  ttl<span style="color:#eceff4">,</span>                  <span style="color:#616e87;font-style:italic">// Use the TTL as the sequence number</span>
</span></span><span style="display:flex;"><span>        Data<span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;HELLO-TRACEROUTE&#34;</span><span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>b<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> m<span style="color:#eceff4">.</span><span style="color:#88c0d0">Marshal</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Marshal failed: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Send the ICMP packet to the destination</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> c<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteTo</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">,</span> dstAddr<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;WriteTo failed: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><h3 id="step-2-receiving-and-validating-the-reply">Step 2: Receiving and Validating the Reply</h3>
<p>After sending the probe, we must wait for a response. We set a read deadline on our connection to act as a timeout. If we receive a packet, we parse it to see what kind of ICMP message it is.</p>
<p>A crucial step is to <strong>validate</strong> that the reply is for a packet <em>we</em> sent. Raw sockets receive all ICMP traffic on the machine, so we could accidentally process a reply meant for another program (like a <code>ping</code> running in another terminal). We do this by checking the ID in the ICMP message, which we set to our process ID (PID). For an <code>Echo Reply</code>, this is straightforward. For a <code>Time Exceeded</code> message, the original packet&rsquo;s ID is nested inside the message body, requiring a bit of parsing to extract.</p>
<p>The following snippet shows the conceptual logic for reading the reply. The full validation is in the complete implementation below.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Wait for a reply</span>
</span></span><span style="display:flex;"><span>reply <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">1500</span><span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// 1500 is the standard MTU (Maximum Transmission Unit) for Ethernet</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ... set read deadline ...</span>
</span></span><span style="display:flex;"><span>n<span style="color:#eceff4">,</span> peer<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> c<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadFrom</span><span style="color:#eceff4">(</span>reply<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// A timeout means we didn&#39;t hear back, continue to next TTL</span>
</span></span><span style="display:flex;"><span>    fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;%d\t*\n&#34;</span><span style="color:#eceff4">,</span> ttl<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Parse the reply message</span>
</span></span><span style="display:flex;"><span>rm<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> icmp<span style="color:#eceff4">.</span><span style="color:#88c0d0">ParseMessage</span><span style="color:#eceff4">(</span><span style="color:#b48ead">1</span><span style="color:#eceff4">,</span> reply<span style="color:#eceff4">[:</span>n<span style="color:#eceff4">])</span> <span style="color:#616e87;font-style:italic">// 1 for ICMPv4</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ... handle parse error ...</span>
</span></span></code></pre></div><h3 id="step-3-the-main-loop-and-interpreting-results">Step 3: The Main Loop and Interpreting Results</h3>
<p>Finally, we wrap the sending and receiving logic in a <code>for</code> loop that increments the TTL from 1 to a maximum value. Before sending a probe, we record the current time, and after receiving a reply, we calculate the duration. This gives us the <strong>round-trip time (RTT)</strong>, which is the total time it takes for the packet to travel to the intermediate router and for the ICMP error message to travel back.</p>
<p>Inside the loop, after receiving and validating a reply, a <code>switch</code> statement checks the type of the ICMP message to determine if we&rsquo;ve found an intermediate router (<code>Time Exceeded</code>) or reached the final destination (<code>Echo Reply</code>).</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Loop from TTL 1 up to a max number of hops (e.g., 64)</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">for</span> ttl <span style="color:#81a1c1">:=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span> ttl <span style="color:#81a1c1">&lt;=</span> <span style="color:#b48ead">64</span><span style="color:#eceff4">;</span> ttl<span style="color:#81a1c1">++</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// ... (Code from Step 1: Craft and Send Probe) ...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// ... (Code from Step 2: Receive and Validate Reply) ...</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    elapsed <span style="color:#81a1c1">:=</span> time<span style="color:#eceff4">.</span><span style="color:#88c0d0">Since</span><span style="color:#eceff4">(</span>start<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#616e87;font-style:italic">// Check the type of the received ICMP message</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">switch</span> rm<span style="color:#eceff4">.</span>Type <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">case</span> ipv4<span style="color:#eceff4">.</span>ICMPTypeEchoReply<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#616e87;font-style:italic">// We&#39;ve reached the final destination</span>
</span></span><span style="display:flex;"><span>        fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;%d\t%v\t%v\n&#34;</span><span style="color:#eceff4">,</span> ttl<span style="color:#eceff4">,</span> peer<span style="color:#eceff4">,</span> elapsed<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#616e87;font-style:italic">// We are done</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">case</span> ipv4<span style="color:#eceff4">.</span>ICMPTypeTimeExceeded<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#616e87;font-style:italic">// This is a reply from an intermediate router</span>
</span></span><span style="display:flex;"><span>        fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;%d\t%v\t%v\n&#34;</span><span style="color:#eceff4">,</span> ttl<span style="color:#eceff4">,</span> peer<span style="color:#eceff4">,</span> elapsed<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">default</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#616e87;font-style:italic">// Other ICMP type</span>
</span></span><span style="display:flex;"><span>        fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;%d\t%v\t%v (type %d)\n&#34;</span><span style="color:#eceff4">,</span> ttl<span style="color:#eceff4">,</span> peer<span style="color:#eceff4">,</span> elapsed<span style="color:#eceff4">,</span> rm<span style="color:#eceff4">.</span>Type<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><h3 id="full-go-implementation">Full Go Implementation</h3>
<p>The following code combines all the steps into a complete, runnable traceroute program, including the crucial validation logic to ensure we only process replies to the probes we sent.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;golang.org/x/net/icmp&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;golang.org/x/net/ipv4&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>os<span style="color:#eceff4">.</span>Args<span style="color:#eceff4">)</span> <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">2</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Usage: go run traceroute.go &lt;destination&gt;&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		os<span style="color:#eceff4">.</span><span style="color:#88c0d0">Exit</span><span style="color:#eceff4">(</span><span style="color:#b48ead">1</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	destination <span style="color:#81a1c1">:=</span> os<span style="color:#eceff4">.</span>Args<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>	dstAddr<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> net<span style="color:#eceff4">.</span><span style="color:#88c0d0">ResolveIPAddr</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;ip4&#34;</span><span style="color:#eceff4">,</span> destination<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Could not resolve destination: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Listen for ICMP packets</span>
</span></span><span style="display:flex;"><span>	c<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> icmp<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenPacket</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;ip4:icmp&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;0.0.0.0&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;ListenPacket failed: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> c<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Traceroute to %s (%s)\n&#34;</span><span style="color:#eceff4">,</span> destination<span style="color:#eceff4">,</span> dstAddr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> ttl <span style="color:#81a1c1">:=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span> ttl <span style="color:#81a1c1">&lt;=</span> <span style="color:#b48ead">64</span><span style="color:#eceff4">;</span> ttl<span style="color:#81a1c1">++</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		start <span style="color:#81a1c1">:=</span> time<span style="color:#eceff4">.</span><span style="color:#88c0d0">Now</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Set TTL</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> c<span style="color:#eceff4">.</span><span style="color:#88c0d0">IPv4PacketConn</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">SetTTL</span><span style="color:#eceff4">(</span>ttl<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;SetTTL failed: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Create ICMP Echo Message</span>
</span></span><span style="display:flex;"><span>		m <span style="color:#81a1c1">:=</span> icmp<span style="color:#eceff4">.</span>Message<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			Type<span style="color:#eceff4">:</span> ipv4<span style="color:#eceff4">.</span>ICMPTypeEcho<span style="color:#eceff4">,</span> Code<span style="color:#eceff4">:</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			Body<span style="color:#eceff4">:</span> <span style="color:#81a1c1">&amp;</span>icmp<span style="color:#eceff4">.</span>Echo<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				ID<span style="color:#eceff4">:</span>   os<span style="color:#eceff4">.</span><span style="color:#88c0d0">Getpid</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">&amp;</span> <span style="color:#b48ead">0xffff</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>				Seq<span style="color:#eceff4">:</span>  ttl<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>				Data<span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;HELLO-TRACEROUTE&#34;</span><span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		b<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> m<span style="color:#eceff4">.</span><span style="color:#88c0d0">Marshal</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Marshal failed: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Send</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> c<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteTo</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">,</span> dstAddr<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;WriteTo failed: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Wait for reply</span>
</span></span><span style="display:flex;"><span>		reply <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">1500</span><span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// 1500 is the standard MTU (Maximum Transmission Unit) for Ethernet</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> c<span style="color:#eceff4">.</span><span style="color:#88c0d0">SetReadDeadline</span><span style="color:#eceff4">(</span>time<span style="color:#eceff4">.</span><span style="color:#88c0d0">Now</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span><span style="color:#b48ead">3</span> <span style="color:#81a1c1">*</span> time<span style="color:#eceff4">.</span>Second<span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;SetReadDeadline failed: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		n<span style="color:#eceff4">,</span> peer<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> c<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadFrom</span><span style="color:#eceff4">(</span>reply<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;%d\t*\t*\t*\n&#34;</span><span style="color:#eceff4">,</span> ttl<span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// Timeout</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		elapsed <span style="color:#81a1c1">:=</span> time<span style="color:#eceff4">.</span><span style="color:#88c0d0">Since</span><span style="color:#eceff4">(</span>start<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Parse the reply message</span>
</span></span><span style="display:flex;"><span>		rm<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> icmp<span style="color:#eceff4">.</span><span style="color:#88c0d0">ParseMessage</span><span style="color:#eceff4">(</span><span style="color:#b48ead">1</span><span style="color:#eceff4">,</span> reply<span style="color:#eceff4">[:</span>n<span style="color:#eceff4">])</span> <span style="color:#616e87;font-style:italic">// 1 for ICMPv4</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error parsing ICMP message: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Check if the reply is for our process and probe</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">switch</span> rm<span style="color:#eceff4">.</span>Type <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> ipv4<span style="color:#eceff4">.</span>ICMPTypeEchoReply<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> rm<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">.(</span><span style="color:#81a1c1">*</span>icmp<span style="color:#eceff4">.</span>Echo<span style="color:#eceff4">).</span>ID <span style="color:#81a1c1">!=</span> os<span style="color:#eceff4">.</span><span style="color:#88c0d0">Getpid</span><span style="color:#eceff4">()</span><span style="color:#81a1c1">&amp;</span><span style="color:#b48ead">0xffff</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">continue</span> <span style="color:#616e87;font-style:italic">// Not our packet</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;%d\t%v\t%v\n&#34;</span><span style="color:#eceff4">,</span> ttl<span style="color:#eceff4">,</span> peer<span style="color:#eceff4">,</span> elapsed<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Destination reached.&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#616e87;font-style:italic">// We are done</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">case</span> ipv4<span style="color:#eceff4">.</span>ICMPTypeTimeExceeded<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			<span style="color:#616e87;font-style:italic">// For simplicity, we assume any TimeExceeded message is for our probe.</span>
</span></span><span style="display:flex;"><span>			<span style="color:#616e87;font-style:italic">// A robust implementation would parse the body of the message</span>
</span></span><span style="display:flex;"><span>			<span style="color:#616e87;font-style:italic">// to verify the ID of the original packet.</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;%d\t%v\t%v\n&#34;</span><span style="color:#eceff4">,</span> ttl<span style="color:#eceff4">,</span> peer<span style="color:#eceff4">,</span> elapsed<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">default</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>			<span style="color:#616e87;font-style:italic">// This could be Destination Unreachable or other types. We&#39;ll ignore them for this simple tool.</span>
</span></span><span style="display:flex;"><span>			fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;%d\t%v\t%v (type %d)\n&#34;</span><span style="color:#eceff4">,</span> ttl<span style="color:#eceff4">,</span> peer<span style="color:#eceff4">,</span> elapsed<span style="color:#eceff4">,</span> rm<span style="color:#eceff4">.</span>Type<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><aside>
See the full source at Github: <a href="https://github.com/sudorandom/kmcd.dev/blob/main/content/posts/2025/traceroute/go/traceroute.go" target="_blank">traceroute.go</a>.

</aside>

<p>This script combines the previous steps into a fully functioning (although not fully featured) traceroute utility. Now it&rsquo;s time to use it.</p>
<h3 id="running-the-code">Running the Code</h3>
<p>Save the code as <code>traceroute.go</code> and execute it with a destination as the argument. Since it requires a raw socket, it must be run with <code>sudo</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sudo go run traceroute.go google.com
</span></span></code></pre></div><p>Here are some of my runs (from a VPN):</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ sudo go run traceroute.go kmcd.dev
</span></span><span style="display:flex;"><span>Traceroute to kmcd.dev <span style="color:#81a1c1">(</span>172.64.80.1<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span><span style="color:#b48ead">1</span>       10.5.0.1        88.094958ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">2</span>       87.249.138.252  88.137959ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">3</span>       79.127.195.58   88.360125ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">4</span>       45.134.215.17   89.163958ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">5</span>       162.158.61.119  90.93775ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">6</span>       172.64.80.1     88.631208ms
</span></span><span style="display:flex;"><span>Destination reached.
</span></span></code></pre></div><p>This is CloudFlare&rsquo;s DNS service.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ sudo go run traceroute.go 1.1.1.1
</span></span><span style="display:flex;"><span>Traceroute to 1.1.1.1 <span style="color:#81a1c1">(</span>1.1.1.1<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span><span style="color:#b48ead">1</span>       10.5.0.1        109.916792ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">2</span>       5.104.76.1      109.879916ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">3</span>       78.152.53.114   110.688917ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">4</span>       207.162.204.138 110.696958ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">5</span>       1.1.1.1 109.922875ms
</span></span><span style="display:flex;"><span>Destination reached.
</span></span></code></pre></div><p>Microsoft.com seems to block ICMP traffic, so we see a lot of timeouts after a certain point:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ sudo go run traceroute.go microsoft.com
</span></span><span style="display:flex;"><span>Traceroute to microsoft.com <span style="color:#81a1c1">(</span>13.107.246.35<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span><span style="color:#b48ead">1</span>       10.5.0.1        110.333709ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">2</span>       5.104.76.1      114.67925ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">3</span>       78.152.53.114   110.453333ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">4</span>       141.136.111.141 121.163833ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">5</span>       62.115.44.160   123.153875ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">6</span>       *       *       *
</span></span><span style="display:flex;"><span><span style="color:#b48ead">7</span>       62.115.140.169  125.062291ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">8</span>       *       *       *
</span></span><span style="display:flex;"><span><span style="color:#b48ead">9</span>       51.10.27.124    121.206958ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">10</span>      104.44.231.105  121.929917ms
</span></span><span style="display:flex;"><span><span style="color:#b48ead">11</span>      *       *       *
</span></span><span style="display:flex;"><span><span style="color:#b48ead">12</span>      *       *       *
</span></span><span style="display:flex;"><span><span style="color:#b48ead">13</span>      *       *       *
</span></span><span style="display:flex;"><span><span style="color:#b48ead">14</span>      *       *       *
</span></span><span style="display:flex;"><span><span style="color:#b48ead">15</span>      *       *       *
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><p>You will often see rows of asterisks like this (<code>* * *</code>). This usually doesn&rsquo;t mean the router is down; it means the router is configured to drop ICMP packets with an expired TTL without sending a response. This is often done for security reasons or to de-prioritize ICMP traffic to protect the router&rsquo;s CPU.</p>
<h3 id="a-note-on-sudo-and-raw-sockets">A Note on <code>sudo</code> and Raw Sockets</h3>
<p>You might ask if <code>sudo</code> is strictly necessary. This is a common point of confusion for developers new to network programming in Go, as tools like the standard <code>traceroute</code> on macOS and Linux can often run without <code>sudo</code> by sending UDP packets. While sending UDP packets is unprivileged, listening for the returning ICMP <code>Time Exceeded</code> errors is a privileged operation that often requires elevated permissions or specific system configurations (like modifying <code>net.ipv4.ping_group_range</code>).</p>
<p>Our code simplifies this by using <code>icmp.ListenPacket(&quot;ip4:icmp&quot;, ...)</code>, which creates a powerful <strong>raw ICMP socket</strong>. This approach requires <code>sudo</code> because listening directly to the entire ICMP protocol is a privileged operation, but it saves us from writing more complex, OS-specific code. This feels more appropriate for this tutorial.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Traceroute is a powerful diagnostic tool that reveals the path our data takes across the internet. By cleverly using the Time-To-Live (TTL) field in IP packets, it turns an error-reporting mechanism into a tool for discovery. We&rsquo;ve walked through how this works, from sending probes with an ever-increasing TTL to interpreting the <code>ICMP Time Exceeded</code> messages that let us map the network hop-by-hop.</p>
<p>Using this knowledge, we built a functional traceroute tool from scratch in Go. Our implementation uses ICMP echo requests, just like the <code>ping</code> utility, and listens for replies from intermediate routers as well as the final destination. While we focused on ICMP, we also touched on alternative probing methods using UDP and TCP.</p>
<p>Building a tool like this from the ground up really demystifies the magic behind everyday network diagnostics and gives a deeper appreciation for the protocols that run the internet. The journey of a single packet is a fascinating one, and with a little bit of Go, we&rsquo;ve built a window to watch it.</p>
<h2 id="next-steps">Next Steps</h2>
<p>While this implementation demonstrates the core logic of traceroute, a production-grade tool would include several enhancements:</p>
<ul>
<li><strong>Reverse DNS Lookup</strong>: The tool currently only shows IP addresses. A reverse-DNS lookup could be added to resolve these IPs into more human-readable hostnames.</li>
<li><strong>Support for UDP and TCP Probes</strong>: Extend the tool to allow different probe methods, such as UDP and TCP, for increased flexibility and compatibility with various network environments and firewalls.</li>
<li><strong>ASN Lookup</strong>: By querying the Autonomous System Number (ASN) of each IP, the tool could identify the specific ISP or organization that owns a router. This enables the visualization of the AS-Path to show how traffic hands off between different entities, for example: <code>Comcast</code> → <code>Tata Communications</code> → <code>Google</code>. Visualizing these organizational jumps is often more insightful than viewing a raw list of IP addresses.</li>
<li><strong>Geo-location</strong>: Beyond simple IP and ASN lookups, more advanced tools can be used to geo-locate routers. By examining router hostnames (which often contain location codes), querying WHOIS databases for IP ranges, and consulting resources like PeeringDB or Internet BGP tables, it&rsquo;s possible to infer the physical location and network ownership of each hop, providing richer diagnostic information.</li>
<li><strong>Concurrency</strong>: To speed up the process, multiple probes could be sent concurrently using goroutines rather than sequentially.</li>
<li><strong>Multiple Probes</strong>: Production tools often send multiple probes per hop and display statistics like average latency and packet loss. Multiple probes would reveal that there may be multiple paths that you are taking, as internet routing can be very dynamic.</li>
</ul>
<p>These features are excellent next steps for expanding this simple tool into a more powerful network diagnostic utility, but this is left as an exercise for the audience. And until then, just use <a href="https://www.bitwizard.nl/mtr/" rel="external">mtr</a>, which has most of these features!</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://www.varonis.com/blog/what-is-traceroute" rel="external">Varonis: What is Traceroute and How Does it Work?</a></li>
<li><a href="https://www.geeksforgeeks.org/what-is-traceroute/" rel="external">GeeksforGeeks: What is Traceroute?</a></li>
<li><a href="https://en.wikipedia.org/wiki/Traceroute" rel="external">Wikipedia: Traceroute</a></li>
<li><a href="https://www.cloudflare.com/learning/network-layer/what-is-traceroute/" rel="external">Cloudflare: What is Traceroute?</a></li>
<li><a href="https://pkg.go.dev/golang.org/x/net/icmp" rel="external">Go Package: <code>golang.org/x/net/icmp</code></a></li>
</ul>
]]></content:encoded></item><item><title>My Favorite Interview Question</title><link>https://kmcd.dev/posts/favorite-interview-question/</link><pubDate>Mon, 22 Sep 2025 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/favorite-interview-question/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/favorite-interview-question/cover_hu_db4c0ea7c272c8cc.webp" /> &lt;/p>
                
                Why I love asking candidates to explain how the internet works.
                </description><content:encoded><![CDATA[<p>Interviews are a strange dance. As an interviewer, you&rsquo;re trying to get a signal on a candidate&rsquo;s skills, experience, and personality in a very short amount of time. As a candidate, you&rsquo;re trying to showcase your best self while under pressure. It&rsquo;s a tough situation for everyone involved.</p>
<p>Over the years, I&rsquo;ve asked a lot of interview questions. Some have been great, some have been duds. But I always come back to my absolute favorite question:</p>
<span class="bigtext">&ldquo;How does the internet work?&rdquo;</span>
<p>I know what you&rsquo;re thinking. That&rsquo;s a huge, open-ended question! And you&rsquo;re right, it is. That&rsquo;s exactly why I love it. You also might be thinking that this is a super common question. I agree! But unlike most questions, this one is still extremely useful despite its popularity.</p>
<h2 id="why-this-question-is-so-powerful">Why this question is so powerful</h2>
<p>This question is a fantastic tool for a few reasons:</p>
<ul>
<li><strong>It&rsquo;s a blank canvas.</strong> The candidate can start anywhere they want. Do they start with the physical layer? The application layer? The philosophy of interconnectedness? It gives them a chance to show me what they think is important.</li>
<li><strong>It reveals their depth of knowledge.</strong> The question can be answered at many different levels of abstraction. A junior engineer might give a high-level overview, while a senior engineer might be able to dive into the nitty-gritty details of TCP handshakes and BGP routing.</li>
<li><strong>It tests communication skills.</strong> Can the candidate take a complex topic and explain it in a clear, concise way? This is a crucial skill for any engineer, especially in a team environment.</li>
<li><strong>It&rsquo;s a conversation starter.</strong> The question often leads to a natural back-and-forth conversation. I can ask follow-up questions to probe deeper into areas where the candidate seems knowledgeable or to help them along if they&rsquo;re struggling.</li>
</ul>
<p>Sometimes, the sheer open-endedness of the question can be a bit overwhelming for a candidate. If they&rsquo;re struggling to start or getting too philosophical, I&rsquo;ll give them a more concrete prompt:</p>
<span class="bigtext">&ldquo;What happens when you type google.com into your browser and press Enter?&rdquo;</span>
<p>This usually gets the ball rolling. Here&rsquo;s a breakdown of the kind of answer I&rsquo;m looking for, with varying levels of detail depending on the candidate&rsquo;s seniority.</p>
<h3 id="what-i-expect-from-most-candidates">What I expect from most candidates</h3>
<p>For any software engineering role that involves web development, I expect the candidate to be able to walk me through the following steps:</p>
<ul>
<li><strong>DNS Lookup:</strong> The browser needs to translate the human-readable domain name <code>google.com</code> into a machine-readable IP address. This involves checking the browser&rsquo;s cache, the OS&rsquo;s cache, and then querying a DNS resolver, which has its own upstream sources and cache.</li>
<li><strong>TCP Connection:</strong> Once the browser has the IP address, it establishes a TCP connection with the server. This involves establishing a connection using TCP&rsquo;s three-way handshake (SYN, SYN-ACK, ACK).</li>
<li><strong>HTTP Request/Response:</strong> The browser sends an HTTP <code>GET</code> request to the server. The server processes the request and sends back an HTTP response, which includes the HTML, CSS, and JavaScript files that make up the Google homepage.</li>
<li><strong>Rendering:</strong> The browser parses the HTML and renders the page. It also executes the JavaScript, which might make additional requests to the server to fetch more data.</li>
</ul>
<h3 id="what-i-hope-for-from-senior-candidates">What I hope for from senior candidates</h3>
<p>For a more senior role, especially at a company that deals with networking or infrastructure, I&rsquo;m hoping for a deeper dive into some of the following topics:</p>
<ul>
<li><strong>ARP (Address Resolution Protocol):</strong> Before the browser can even send a packet to the router, it needs to know the router&rsquo;s MAC address. This is where ARP comes in.</li>
<li><strong>DHCP (Dynamic Host Configuration Protocol):</strong> - How did our computer get an IP address in the first place? A senior candidate might explain that the machine likely requested an IP address, a subnet mask, the default gateway&rsquo;s IP, and the DNS server&rsquo;s IP from a DHCP server on the local network.</li>
<li><strong>NAT (Network Address Translation):</strong> - To conserve the limited global supply of IPv4 addresses, most home and office networks use a single public IP address for many devices. NAT is the mechanism that allows a router to translate between private, internal IP addresses and that single public one.</li>
<li><strong>BGP (Border Gateway Protocol):</strong> How does my request find its way from my local network to Google&rsquo;s servers, potentially halfway across the world? Mentioning BGP shows an understanding that the internet is a &rsquo;network of networks&rsquo; and that routing between these large autonomous systems is a complex, solved problem.</li>
<li><strong>HTTP/2 and HTTP/3 (QUIC):</strong> Is the connection really just a single TCP connection? A senior candidate might discuss the limitations of TCP, like head-of-line blocking, and how newer protocols solve them. Pointing out that since the destination is Google, the connection is likely using HTTP/3 (which runs on QUIC over UDP) is a huge plus.</li>
<li><strong>TCP Keep-Alives:</strong> How does the connection stay open for subsequent requests? What are the trade-offs of keep-alives?</li>
<li><strong>Caching:</strong> Where does caching happen in this whole process? DNS caching, browser caching, CDN caching&hellip; there are many layers.</li>
<li><strong>Pre-flight Requests (CORS):</strong> If the page makes requests to other domains, the browser might need to make a pre-flight <code>OPTIONS</code> request to check if the cross-origin request is allowed.</li>
<li><strong>Packet Breakdown:</strong> What does an actual packet look like? What are the different headers (Ethernet, IP, TCP, HTTP)?</li>
</ul>
<h2 id="its-not-about-getting-it-right">It&rsquo;s not about getting it &ldquo;right&rdquo;</h2>
<p>I want to be clear: I don&rsquo;t expect any candidate to know everything about the internet. That&rsquo;s impossible. What I&rsquo;m looking for is a candidate who has a solid foundation, can reason about complex systems, and is curious to learn more. How does the candidate behave when I press into an area that they don&rsquo;t know a lot about? Are they curious? Are they willing to say they don&rsquo;t know? Do they make things up? All of that is very useful for evaluating skill and knowledge level.</p>
<p>Sometimes a candidate&rsquo;s answer goes in a completely unexpected direction. I once had a candidate describe the hardware interrupts and OS context switches that happen just from typing &lsquo;google.com&rsquo;. Even though it was a tangent, it was a valuable signal that showed me how they reason about systems from the hardware up.</p>
<p>This question is a fantastic way to gauge all of those things. It&rsquo;s a journey we go on together, and I often learn something new from the candidate&rsquo;s answer.</p>
<p>What&rsquo;s your go-to interview question? I&rsquo;d love to hear it.</p>
]]></content:encoded></item><item><title>From JSON to Protobuf</title><link>https://kmcd.dev/posts/from-json-to-protobuf/</link><pubDate>Tue, 02 Sep 2025 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/from-json-to-protobuf/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/from-json-to-protobuf/cover_hu_40e13ae2e18fb743.webp" /> &lt;/p>
                
                A Tool to Ease Your Schema Journey
                </description><content:encoded><![CDATA[<p>When I first approached the possibility of using protobuf or gRPC, I was intimidated by a few things. First, it was the terrible tooling: Protoc was weird, the plugin system it used was odd, the source of binaries was from wildly different places depending on the platform, and the code it generated was insane. However, <a href="https://buf.build/product/cli" rel="external">the buf CLI</a> fixes most of these issues for me. The next challenge I distinctly remember was getting started with my own Protobuf definitions. The tutorials were perfectly fine, but once you are set off on your own it&rsquo;s kind of hard to know what to do next. I ended up reading the spec, since it&rsquo;s not all that long. But I do feel like others don&rsquo;t learn well from that method. Others will learn from examples. And the best examples are those that build on a foundation of knowledge they already have.</p>
<p>This is the problem I built a tool to solve. JSON has become an inescapable reality for developers of all types. It is very hard to become a junior developer without seeing and using a decent amount of JSON. Therefore, I figured that JSON is a good starting point for explaining protobufs. They are similar in many ways; in fact, there is now a <a href="https://protobuf.dev/programming-guides/json/" rel="external">standard way to convert protobuf types into JSON</a>. That said, you may know that protobuf has some differences. First, it has an encoding that is more efficient than JSON, in terms of space and often in terms of processing time. Second, it is a schema-driven format. Protobuf files can be used to generate types for code in most programming languages and can be used to automatically generate documentation and other kinds of artifacts. This is a powerful concept that enables the sharing of types to many different languages. This approach reduces errors, reduces the amount of duplicate code that you have to write and maintain for common types and it enables some more efficient storage and processing of data.</p>
<p>The tool I built is meant to be a bridge for those stuck in the world of schema-less JSON, guiding them into a world of type safety and all the other benefits that Protobufs provide. I introduce, <a href="https://json-to-proto.kmcd.dev/" rel="external">json-to-proto.kmcd.dev</a>. This website/tool will take example JSON documents and produces a protobuf file that can consume and emit the same JSON. The goal here is mostly for education; to translate something junior developers know (how to read JSON) to something that they might not know (how to make the equivalent protobuf file). I&rsquo;m hoping that it will provide enough material to get developers started.</p>
<figure><a href="https://kmcd.dev/posts/from-json-to-protobuf/screenshot.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/from-json-to-protobuf/screenshot_hu_c8288ca7062faede.png"
         alt="json-to-proto.kmcd.dev"/>
    </a><figcaption>
            <p><a href="https://json-to-proto.kmcd.dev/" rel="external">json-to-proto.kmcd.dev</a></p>
        </figcaption>
</figure>

<span class="bigtext">Give it a try at <a href="https://json-to-proto.kmcd.dev/" rel="external">json-to-proto.kmcd.dev</a>!</span>
<div class="warning-box">
  Use this as an educational tool. <em>Understand your protobufs before you ship</em>.
</div>

<hr>
<h2 id="how-it-works">How it works</h2>
<p>The magic of this tool lies in its straightforward approach to translating the flexible world of JSON into the structured world of Protobuf. It does this by applying a series of simple rules and heuristics to the input data. The core of this process is type mapping, where the tool identifies each JSON data type (a string, number, boolean, or object) and converts it to a suitable Protobuf counterpart.</p>
<h3 id="json-string--protobuf-string">JSON String → Protobuf string</h3>
<p>All JSON strings are converted to the Protobuf <code>string</code> type.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;username&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;joe&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">my_package</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">UserProfile</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> username <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>This is a straightforward mapping. The tool recognizes the string value <code>&quot;joe&quot;</code> and translates the field to the corresponding <code>string</code> type in Protobuf, a simple and direct conversion that works for text of all kinds.</p>
<h3 id="json-number--protobuf-int64-or-double">JSON Number → Protobuf int64 or double</h3>
<p>The converter maps integer numbers to <code>int64</code> and floating-point numbers to <code>double</code>. The use of <code>int64</code> and <code>double</code> is a safe choice to ensure both large numbers and decimal values are handled correctly.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;userId&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">12345</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;gpa&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">3.4</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">my_package</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">UserProfile</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">int64</span> userId <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">double</span> gpa <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><h3 id="json-boolean--protobuf-bool">JSON Boolean → Protobuf bool</h3>
<p>JSON boolean values (<code>true</code> and <code>false</code>) are converted to the Protobuf <code>bool</code> type.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;graduated&#34;</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">false</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;enrolled&#34;</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">my_package</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">UserProfile</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">bool</span> graduated <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">bool</span> enrolled <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>Like strings, booleans have a direct Protobuf equivalent. The tool sees the <code>true</code> and <code>false</code> values and maps them directly to a <code>bool</code> type, making this a simple and lossless conversion.</p>
<h3 id="json-object--protobuf-message">JSON Object → Protobuf message</h3>
<p>Any JSON object is mapped to a new, nested Protobuf <code>message</code> type. The name of the new message is derived from the object&rsquo;s field name. I&rsquo;ve been using objects for all of my examples since they are essentially required at the top level. However, there&rsquo;s something interesting to note about the attribute names. If the JSON attributes don&rsquo;t match in terms of capitalization and underscore usage that protojson uses, json-to-proto.kmcd.dev will add a <code>json_name</code> annotation to make it match up correctly.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;user_id&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">12345</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;UserName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;joe&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;ENROLLED&#34;</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">my_package</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">UserProfile</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">int64</span> user_id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span> <span style="color:#eceff4">[</span>json_name <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;user_id&#34;</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> user_name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span> <span style="color:#eceff4">[</span>json_name <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;UserName&#34;</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">bool</span> enrolled <span style="color:#81a1c1">=</span> <span style="color:#b48ead">3</span> <span style="color:#eceff4">[</span>json_name <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;ENROLLED&#34;</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>This is where the tool gets clever with its handling of naming conventions. Notice how <code>UserName</code> is converted to <code>user_name</code> in the Protobuf file but keeps its original name in the JSON with a <code>json_name</code> option. This ensures the generated Protobuf still works seamlessly with your existing JSON data, a powerful feature for maintaining compatibility.</p>
<h3 id="json-array--protobuf-repeated">JSON Array → Protobuf repeated</h3>
<p>A JSON array is converted into a <code>repeated</code> field in Protobuf. The type of the elements within the <code>repeated</code> field is inferred from the data contained in the array.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;courses&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;courseId&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;CS101&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;courseName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Intro to CS&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;courseId&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;MA203&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;courseName&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Linear Algebra&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;credits&#34;</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">null</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;login_timestamps&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#b48ead">1679400000</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#b48ead">1679486400</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;mixed_data&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#b48ead">1</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;test&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;key&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;value&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">null</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#a3be8c">&#34;google/protobuf/struct.proto&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">my_package</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">Course</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">repeated</span> <span style="color:#81a1c1">string</span> course_id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">repeated</span> <span style="color:#81a1c1">string</span> course_name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">repeated</span> google.protobuf.Value credits <span style="color:#81a1c1">=</span> <span style="color:#b48ead">3</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">UserProfile</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">repeated</span> Course courses <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">repeated</span> <span style="color:#81a1c1">int64</span> login_timestamps <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span> <span style="color:#eceff4">[</span>json_name <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;login_timestamps&#34;</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">repeated</span> google.protobuf.Value mixed_data <span style="color:#81a1c1">=</span> <span style="color:#b48ead">3</span> <span style="color:#eceff4">[</span>json_name <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;mixed_data&#34;</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>This example shows three of these cases:</p>
<ul>
<li><code>courses</code> has a repeated field of <code>Course</code> messages.</li>
<li><code>login_timestamps</code> as a repeated field with <code>int64</code> values.</li>
<li><code>mixed_data</code> as a repeated field of <code>google.protobuf.Value</code> messages, because the JSON array has several different types.</li>
</ul>
<p>Another edge-case is hit here as well. The <code>Course</code> message has a <code>credits</code> field of type <code>google.protobuf.Value</code>. Why? Because the input JSON only has that field as being <code>null</code>. So the tool doesn&rsquo;t have enough information to know what type <code>&quot;credits&quot;</code> should be.</p>
<p>Here, the tool&rsquo;s logic for handling arrays shines. A <code>repeated</code> field is Protobuf&rsquo;s way of representing a list. The tool infers the types within each array: a custom <code>Course</code> message, a list of <code>int64</code> values, and a catch-all <code>google.protobuf.Value</code> for the <code>mixed_data</code> array, which contains different data types. To get the most accurate protobuf schema, it&rsquo;s best to provide a representative JSON example that includes all possible data types and structures.</p>
<hr>
<h2 id="when-you-shouldnt-use-this-tool">When you shouldn&rsquo;t use this tool</h2>
<p>It&rsquo;s crucial to acknowledge that a tool like this is not a perfect replacement for human judgment. It is a powerful starting point, but a developer must always review and refine the output to ensure it perfectly matches the application&rsquo;s needs.</p>
<h3 id="type-inference">Type Inference</h3>
<p>While the tool makes an intelligent guess about types, it can&rsquo;t read your mind. For example, a JSON number might be inferred as an <code>int64</code>, but your application may only ever need an <code>int32</code>. A developer should always make the final decision based on their knowledge of the data.</p>
<h3 id="map-vs-message">map vs. message</h3>
<p>The tool defaults to a <code>message</code> for JSON objects. A developer might need to manually change this to a <code>map</code> if the object&rsquo;s keys are truly arbitrary (e.g., a dictionary of feature flags).</p>
<hr>
<h2 id="a-starting-point-not-a-destination">A Starting Point, Not a Destination</h2>
<p>In a world where JSON&rsquo;s flexibility often leads to schema-less chaos, Protobuf offers a path to efficiency, speed, and type safety. My tool is designed to serve as a powerful bridge, providing a smooth on-ramp for developers who are intimidated by the initial hurdle of defining a <code>.proto</code> schema from scratch.</p>
<p>I encourage you to give it a try. The tool is a powerful assistant that eliminates the tedious first steps, freeing you up to focus on what matters most: <strong>building robust, scalable applications with a solid schema as their foundation.</strong></p>
]]></content:encoded></item><item><title>Breaking gRPC</title><link>https://kmcd.dev/posts/breaking-grpc/</link><pubDate>Tue, 05 Aug 2025 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/breaking-grpc/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/breaking-grpc/cover_hu_72d86e0dbbafcc2.webp" /> &lt;/p>
                
                How to avoid breaking gRPC clients.
                </description><content:encoded><![CDATA[<p>When we use gRPC, we often praise its efficiency and strong contracts defined by Protocol Buffers (<code>.proto</code> files). We know that gRPC uses protobuf&rsquo;s binary format for fast, compact, and forward/backward-compatible communication. But what happens when you expose your gRPC service to clients who speak JSON, like a web frontend?</p>
<p>The encoding you use (binary protobuf or transcoded JSON) dramatically changes the rules of what constitutes a &ldquo;safe&rdquo; or &ldquo;breaking&rdquo; change to your API. A change that is perfectly harmless for a protobuf client can completely break a JSON client. Let&rsquo;s dig into this more.</p>
<h2 id="how-encodings-work">How Encodings Work</h2>
<p>First, a quick refresher on how each format represents data. Consider this simple protobuf message:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">my_service</span><span style="color:#81a1c1">.</span>v1<span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">User</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#616e87;font-style:italic">// A unique identifier for the user.
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span>  <span style="color:#81a1c1">int64</span> user_id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#616e87;font-style:italic">// The user&#39;s full name.
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span>  <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><h3 id="protobuf-its-all-about-the-numbers">Protobuf: It&rsquo;s All About the Numbers</h3>
<p>On the wire, the binary protobuf encoding doesn&rsquo;t care about the field names (<code>user_id</code>, <code>name</code>). It only cares about the <strong>field numbers</strong> (<code>1</code>, <code>2</code>) and their wire types. A simplified view of the encoded data is a series of key-value pairs where the key is the field number. I dig into this further in my <a href="https://kmcd.dev/posts/grpc-from-scratch-part-3/">gRPC from Scratch</a> series, where I discuss the binary protobuf encoding.</p>
<p>Because of this, you can rename a field in your <code>.proto</code> file, and as long as the field number and type remain the same, it&rsquo;s a <strong>non-breaking change</strong> for protobuf clients.</p>
<h3 id="json-its-all-about-the-names">JSON: It&rsquo;s All About the Names</h3>
<p>When a gRPC gateway or library transcodes this message to JSON, it produces a standard JSON object. JSON is also a perfectly valid encoding to use with gRPC. By default, it uses the protobuf field names (converted to <code>lowerCamelCase</code>) as the JSON keys:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;userId&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">12345</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;name&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Alex&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Since JSON clients are coupled to these names, changing them will inevitably break the integration. <strong>JSON clients are coupled to field names, not field numbers.</strong> This fundamental difference is the source of many potential compatibility issues.</p>
<h2 id="analyzing-api-changes-breaking-vs-non-breaking">Analyzing API Changes: Breaking vs. Non-Breaking</h2>
<p>Let&rsquo;s look at common changes you might make to a <code>.proto</code> file and see their impact on each encoding.</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Change</th>
          <th style="text-align: left">Protobuf Impact</th>
          <th style="text-align: left">JSON Impact</th>
          <th style="text-align: left">Explanation</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><strong>Renaming a field</strong> (<code>name</code> to <code>full_name</code>)</td>
          <td style="text-align: left">✅ <strong>Non-breaking</strong></td>
          <td style="text-align: left">💥 <strong>Breaking</strong></td>
          <td style="text-align: left">Protobuf clients only see the field number (<code>2</code>), which hasn&rsquo;t changed. JSON clients expect the key <code>&quot;name&quot;</code> but will now see <code>&quot;fullName&quot;</code>.</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Changing a field number</strong> (<code>= 2</code> to <code>= 3</code>)</td>
          <td style="text-align: left">💥 <strong>Breaking</strong></td>
          <td style="text-align: left">✅ <strong>Non-breaking</strong></td>
          <td style="text-align: left">This is a cardinal sin in the protobuf world. A client expecting field <code>2</code> will no longer find it. JSON clients, however, still see the key <code>&quot;name&quot;</code> and are unaffected.</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Adding a new field</strong> (<code>email = 3</code>)</td>
          <td style="text-align: left">✅ <strong>Non-breaking</strong></td>
          <td style="text-align: left">✅ <strong>Non-breaking</strong></td>
          <td style="text-align: left">Well-behaved clients in both formats are designed to ignore unknown fields, making this a safe operation.</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Removing or deprecating a field</strong></td>
          <td style="text-align: left">✅ <strong>Non-breaking</strong></td>
          <td style="text-align: left">✅ <strong>Non-breaking</strong></td>
          <td style="text-align: left">Similar to adding a field, clients should handle missing fields gracefully. It&rsquo;s best practice to <code>deprecate</code> a field before removing it.</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Changing a compatible type</strong> (<code>int32</code> to <code>int64</code>)</td>
          <td style="text-align: left">✅ <strong>Non-breaking</strong></td>
          <td style="text-align: left">✅ <strong>Non-breaking</strong></td>
          <td style="text-align: left">These types have compatible wire formats in protobuf. For JSON, both are simply numbers, so there&rsquo;s no issue.</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Changing an incompatible type</strong> (<code>int64</code> to <code>string</code>)</td>
          <td style="text-align: left">💥 <strong>Breaking</strong></td>
          <td style="text-align: left">💥 <strong>Breaking</strong></td>
          <td style="text-align: left">The wire format for a number and a string are different, breaking protobuf clients. The data type in JSON also changes (e.g., <code>123</code> vs. <code>&quot;123&quot;</code>), which will break any client expecting a number.</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="the-solution-decouple-names-with-json_name">The Solution: Decouple Names with <code>json_name</code></h2>
<p>So, how do you refactor your <code>.proto</code> field names without breaking your JSON clients? The protobuf specification provides a simple and elegant solution: the <strong><code>json_name</code></strong> field option.</p>
<p>This option lets you explicitly set the JSON key for a field, decoupling it from the <code>.proto</code> field name.</p>
<p>Let&rsquo;s revise our <code>User</code> message. Suppose we want to rename <code>name</code> to <code>full_name</code> for clarity in our Go or Python code, but we can&rsquo;t break existing JSON clients that rely on the <code>&quot;name&quot;</code> key.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">my_service</span><span style="color:#81a1c1">.</span>v1<span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">User</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">int64</span> user_id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span> <span style="color:#eceff4">[</span>json_name <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;userId&#34;</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#616e87;font-style:italic">// The field is now &#39;full_name&#39; in code, but will still be &#39;name&#39; in JSON.
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span>  <span style="color:#81a1c1">string</span> full_name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span> <span style="color:#eceff4">[</span>json_name <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;name&#34;</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>With <code>json_name = &quot;name&quot;</code>, we&rsquo;ve instructed the transcoder to do the following:</p>
<ol>
<li><strong>For Protobuf:</strong> Continue using field number <code>2</code>. The field name <code>full_name</code> is used by the code generator.</li>
<li><strong>For JSON:</strong> Always use the key <code>&quot;name&quot;</code> during serialization, regardless of what the <code>.proto</code> field is called.</li>
</ol>
<p>Now, you are free to change the <code>full_name</code> field to something else (e.g., <code>user_display_name</code>) in the future, and your JSON contract remains stable.</p>
<h2 id="automating-your-safety-net-with-buf-breaking">Automating Your Safety Net with <code>buf breaking</code></h2>
<p>Remembering all these nuanced rules across different encodings is difficult and error-prone. This is where automated tooling becomes essential. The popular <a href="https://buf.build/" rel="external">Buf toolchain</a> includes a powerful command, <strong><code>buf breaking</code></strong>, designed specifically for this problem.</p>
<p>The <code>buf breaking</code> command compares your current <code>.proto</code> files against a previous state (like your main git branch) and reports any changes that would break your API consumers. Crucially, it understands that &ldquo;breaking&rdquo; means different things to different clients. You can configure it to check against multiple compatibility strategies.</p>
<p>In your <code>buf.yaml</code> configuration file, you can specify which rule sets to check against:</p>
<ul>
<li><code>FILE</code>: Checks for backward-incompatible changes at the <code>.proto</code> file level, like deleting a field or changing a field number. This protects your <strong>protobuf-based clients</strong>.</li>
<li><code>WIRE_JSON</code>: Checks for backward-incompatible changes for the JSON wire format. This catches things like renaming a field without using <code>json_name</code>. This protects your <strong>JSON-based clients</strong>.</li>
<li><code>PACKAGE</code>: Checks for source-code-level breaking changes in the generated stubs for languages like Go and Java. This protects the <strong>developers using your generated code</strong>.</li>
</ul>
<p>A typical configuration for a service with both gRPC and JSON clients might look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># buf.yaml</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">version</span><span style="color:#eceff4">:</span> v2
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">breaking</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">use</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>    - FILE
</span></span><span style="display:flex;"><span>    - WIRE_JSON
</span></span></code></pre></div><p>By integrating <code>buf breaking</code> into your CI/CD pipeline, you can automatically prevent developers from merging changes that would break any of your consumers, whether they speak protobuf or JSON.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Evolving an API for both protobuf and JSON clients is a recipe for a very specific kind of headache, the kind that pages you at 3 AM. You&rsquo;ve got protobuf, which only cares about numbers, and JSON, which only cares about names. A &ldquo;safe&rdquo; refactor for one is a production-breaking slap in the face for the other. This is where a schema-first approach, backed by powerful schema-aware tooling, isn&rsquo;t just a good idea; it&rsquo;s the only thing keeping you from questioning all your life choices.</p>
<p>Protobuf&rsquo;s semantics, like the <code>json_name</code> option, give you a powerful escape hatch. It makes certain refactors, like renaming a field for internal clarity, trivial <em>if</em> you have the right tooling in place. You can change your code without your JSON clients ever knowing you touched a thing. This decoupling is a superpower, but only if you use it correctly.</p>
<p>And that&rsquo;s the catch: don&rsquo;t rely on developers&rsquo; goldfish sized memory or manual code reviews to enforce these complex, conflicting rules. That&rsquo;s how you break production at 3 AM. Instead, let the robots do the heavy lifting. Integrating a tool like <code>buf breaking</code> into your CI pipeline is like having an unblinking, unforgiving guardian for your API. It understands the different breaking change rules for both protobuf and JSON and will stop a bad change before it ever gets merged. This is the real strength of a schema-first workflow: it makes complex refactors not just possible, but safe. You can merge with confidence and keep all your clients (binary or JSON) happy.</p>
]]></content:encoded></item><item><title>Morse Code</title><link>https://kmcd.dev/posts/morse/</link><pubDate>Tue, 22 Jul 2025 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/morse/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/morse/cover_hu_724955e1dc30dcfb.webp" /> &lt;/p>
                
                Explore the history of Morse code, from its dits and dahs to the first transatlantic cable. Discover how this elegant system was the original binary and test your own skills with an interactive speed typer.
                </description><content:encoded><![CDATA[<p>I&rsquo;m continuing my journey into discovering more about older technology by making silly games. This time, I made a website that allows you to test your speed skills in the most useful skill ever! Morse code! Now you can test your skills in writing and interpreting morse code! And it even gives you your (likely abysmal) words per minute (wpm). (Plus, it&rsquo;s great practice for <a href="https://keeptalkinggame.com/" rel="external">Keep Talking and Nobody Explodes</a>)</p>
<p>Head over to <a href="https://morse.kmcd.dev" rel="external">https://morse.kmcd.dev</a> and try the <a href="https://morse.kmcd.dev" rel="external">Morse Speed Typer</a> today! And if you need to brush up on your skills, check out the <a href="https://morse.kmcd.dev/learn" rel="external">learning page</a>.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style=" width:500px;">
<a href="https://morse.kmcd.dev" target="_blank">

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/morse/screenshot_hu_c3ae04bcc1934191.webp"
             alt="Screenshot of morse.kmcd.dev"/>
    


</a>

    </span>

    
</div>

<hr>
<h2 id="what-is-morse-code">What is Morse Code?</h2>
<p>Morse code is a character encoding system used in telecommunication that represents letters, numbers, and punctuation marks as sequences of short and long signals. These signals, often called <strong>dots</strong> (or &ldquo;dits&rdquo;) and <strong>dashes</strong> (or &ldquo;dahs&rdquo;), can be transmitted in various ways, such as through sound, light flashes, or electrical pulses. A simple and universally recognized example is the distress signal SOS, which is represented as <code>... --- ...</code>.</p>
<p>Developed in the 1830s by Samuel Morse and Alfred Vail, it revolutionized long-distance communication. Before we had fiber optics and satellites, we had the simple, elegant language of dits and dahs clicking away across telegraph wires.</p>
<hr>
<h2 id="the-rhythm-of-the-code-timing-">The Rhythm of the Code: Timing ⏱️</h2>
<p>Morse code isn&rsquo;t just about the dots and dashes; it&rsquo;s about the silence in between. The timing is crucial for distinguishing letters and words. The entire system is based on the length of a single dot.</p>
<ul>
<li>A <strong>dot</strong> (dit) is the basic time unit: 1 unit long.</li>
<li>A <strong>dash</strong> (dah) is three times longer than a dot: 3 units long.</li>
<li>The <strong>space</strong> between parts of the same letter (e.g., the gap between the <code>.</code> and <code>-</code> in &lsquo;A&rsquo;) is 1 unit long.</li>
<li>The <strong>space</strong> between letters in a word is 3 units long.</li>
<li>The <strong>space</strong> between words is 7 units long.</li>
</ul>
<p>This precise rhythm is what allows a trained operator to &ldquo;read&rdquo; the code by ear.</p>
<hr>
<h2 id="the-morse-alphabet">The Morse Alphabet</h2>
<p>Just like in modern data compression, Morse code was designed with efficiency in mind. The most frequently used letters in the English language (like E and T) are assigned the shortest Morse code sequences, while less common letters have longer, more complex patterns. This clever design allowed for faster and more efficient communication.</p>
<div class="morse-table">
    <div class="morse-column">
        <table>
            <thead>
                <tr>
                    <th>Character</th>
                    <th>Morse Code</th>
                </tr>
            </thead>
            <tbody>
                <tr><td>A</td><td>.-</td></tr>
                <tr><td>B</td><td>-...</td></tr>
                <tr><td>C</td><td>-.-.</td></tr>
                <tr><td>D</td><td>-..</td></tr>
                <tr><td>E</td><td>.</td></tr>
                <tr><td>F</td><td>..-.</td></tr>
                <tr><td>G</td><td>--.</td></tr>
                <tr><td>H</td><td>....</td></tr>
                <tr><td>I</td><td>..</td></tr>
                <tr><td>J</td><td>.---</td></tr>
                <tr><td>K</td><td>-.-</td></tr>
                <tr><td>L</td><td>.-..</td></tr>
                <tr><td>M</td><td>--</td></tr>
            </tbody>
        </table>
    </div>
    <div class="morse-column">
        <table>
            <thead>
                <tr>
                    <th>Character</th>
                    <th>Morse Code</th>
                </tr>
            </thead>
            <tbody>
                <tr><td>N</td><td>-.</td></tr>
                <tr><td>O</td><td>---</td></tr>
                <tr><td>P</td><td>.--.</td></tr>
                <tr><td>Q</td><td>--.-</td></tr>
                <tr><td>R</td><td>.-.</td></tr>
                <tr><td>S</td><td>...</td></tr>
                <tr><td>T</td><td>-</td></tr>
                <tr><td>U</td><td>..-</td></tr>
                <tr><td>V</td><td>...-</td></tr>
                <tr><td>W</td><td>.--</td></tr>
                <tr><td>X</td><td>-..-</td></tr>
                <tr><td>Y</td><td>-.--</td></tr>
                <tr><td>Z</td><td>--..</td></tr>
            </tbody>
        </table>
    </div>
    <div class="morse-column">
        <table>
            <thead>
                <tr>
                    <th>Number</th>
                    <th>Morse Code</th>
                </tr>
            </thead>
            <tbody>
                <tr><td>0</td><td>-----</td></tr>
                <tr><td>1</td><td>.----</td></tr>
                <tr><td>2</td><td>..---</td></tr>
                <tr><td>3</td><td>...--</td></tr>
                <tr><td>4</td><td>....-</td></tr>
                <tr><td>5</td><td>.....</td></tr>
                <tr><td>6</td><td>-....</td></tr>
                <tr><td>7</td><td>--...</td></tr>
                <tr><td>8</td><td>---..</td></tr>
                <tr><td>9</td><td>----.</td></tr>
            </tbody>
        </table>
    </div>
</div>
<hr>
<h2 id="the-original-binary-code">The Original Binary Code</h2>
<div class="diagram-wrapper">
    <span class="diagram"
        style=" width:500px;">

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/morse/operators_hu_23b63c841f537671.webp"
             alt="Illustration of telegraph operators"/>
    



    </span>

    
</div>

<p>Long before computers used 1s and 0s, Morse code was encoding information using just two states: a short signal and a long signal (or, more fundamentally, &ldquo;signal on&rdquo; and &ldquo;signal off&rdquo;). In this sense, Morse code is one of the earliest forms of a <strong>binary code</strong>.</p>
<p>Just as modern computers use a standardized system like ASCII or Unicode to map binary digits to characters, Morse code provides a map from dits and dahs to the letters of the alphabet. It&rsquo;s a brilliant, early example of how complex information like human language can be broken down into simple, transmittable units.</p>
<hr>
<h2 id="cool-facts-from-the-world-of-morse-">Cool Facts from the World of Morse 🌍</h2>
<p>Morse code has a rich history filled with interesting trivia and conventions.</p>
<ul>
<li>
<p><strong>SOS is not an acronym.</strong> The famous distress signal, <code>...---...</code>, was chosen because its pattern is simple and unmistakable. The continuous sequence of three dots, three dashes, and three dots is easy to recognize even through heavy static or interference. It doesn&rsquo;t stand for &ldquo;Save Our Ship&rdquo; or &ldquo;Save Our Souls,&rdquo; though those are memorable mnemonics!</p>

    <div class="morse-viz">
      <div class="morse-container morse-viz-flex" style="display: flex; align-items: center; gap: 1.5rem;">
        
        
          <span class="morse-label">SOS</span>
        
        <div class="morse-viz-signals" style="display: flex; align-items: center;">
          
          
            
            
    
            
            
              
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                </span>
              </div>
            
          
        </div>
      </div>
    </div>
</li>
<li>
<p><strong>K (Invitation to Transmit).</strong> In Morse conversations, sending a single letter &lsquo;K&rsquo; (<code>-.-</code>) is an invitation for the other person to start transmitting. It&rsquo;s the equivalent of saying &ldquo;over&rdquo; or &ldquo;your turn.&rdquo;</p>

    <div class="morse-viz">
      <div class="morse-container morse-viz-flex" style="display: flex; align-items: center; gap: 1.5rem;">
        
        
          <span class="morse-label">K</span>
        
        <div class="morse-viz-signals" style="display: flex; align-items: center;">
          
          
            
            
    
            
            
              
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                  
                </span>
              </div>
            
          
        </div>
      </div>
    </div>
</li>
<li>
<p><strong>73 and 88.</strong> The world of ham radio, which heavily uses Morse code, developed its own numeric shorthand. <code>73</code> means &ldquo;Best regards,&rdquo; and <code>88</code> means &ldquo;Love and kisses,&rdquo; typically used when signing off with a close friend or partner.</p>
<p>
    <div class="morse-viz">
      <div class="morse-container morse-viz-flex" style="display: flex; align-items: center; gap: 1.5rem;">
        
        
          <span class="morse-label">73</span>
        
        <div class="morse-viz-signals" style="display: flex; align-items: center;">
          
          
            
            
    
            
            
              
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                </span>
              </div>
            
          
        </div>
      </div>
    </div>

    <div class="morse-viz">
      <div class="morse-container morse-viz-flex" style="display: flex; align-items: center; gap: 1.5rem;">
        
        
          <span class="morse-label">88</span>
        
        <div class="morse-viz-signals" style="display: flex; align-items: center;">
          
          
            
            
    
            
            
              
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                </span>
              </div>
            
          
        </div>
      </div>
    </div></p>
</li>
<li>
<p><strong>What hath God wrought!</strong> On May 24, 1844, this was the first official message sent by Samuel Morse on the telegraph line between Washington, D.C., and Baltimore. The phrase, from the Book of Numbers, was suggested by Annie Ellsworth, the daughter of a friend. Here’s the message in Morse:</p>
<p>
    <div class="morse-viz">
      <div class="morse-container morse-viz-flex" style="display: flex; align-items: center; gap: 1.5rem;">
        
        
          <span class="morse-label">WHAT</span>
        
        <div class="morse-viz-signals" style="display: flex; align-items: center;">
          
          
            
            
    
            
            
              
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                </span>
              </div>
            
          
        </div>
      </div>
    </div>

    <div class="morse-viz">
      <div class="morse-container morse-viz-flex" style="display: flex; align-items: center; gap: 1.5rem;">
        
        
          <span class="morse-label">HATH</span>
        
        <div class="morse-viz-signals" style="display: flex; align-items: center;">
          
          
            
            
    
            
            
              
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                </span>
              </div>
            
          
        </div>
      </div>
    </div>

    <div class="morse-viz">
      <div class="morse-container morse-viz-flex" style="display: flex; align-items: center; gap: 1.5rem;">
        
        
          <span class="morse-label">GOD</span>
        
        <div class="morse-viz-signals" style="display: flex; align-items: center;">
          
          
            
            
    
            
            
              
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                </span>
              </div>
            
          
        </div>
      </div>
    </div>

    <div class="morse-viz">
      <div class="morse-container morse-viz-flex" style="display: flex; align-items: center; gap: 1.5rem;">
        
        
          <span class="morse-label">WROUGHT</span>
        
        <div class="morse-viz-signals" style="display: flex; align-items: center;">
          
          
            
            
    
            
            
              
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                    
                    
                      <span class="morse-ispace" title="intra-character space"></span>
                    
                    
                    
                      <span class="morse-dit" title="dit"></span>
                    
                  
                </span>
              </div>
            
              
              
                <div class="morse-lspace" title="inter-character space"></div>
              
              <div class="morse-char">
                <span style="display: flex; align-items: center;">
                  
                    
                    
                    
                    
                      <span class="morse-dah" title="dah"></span>
                    
                  
                </span>
              </div>
            
          
        </div>
      </div>
    </div></p>
</li>
</ul>
<hr>
<h2 id="conquering-the-ocean-the-first-global-connection">Conquering the Ocean: The First Global Connection</h2>
<p>It’s easy to take for granted that we can send a message across the world in an instant. But before the 1860s, the fastest way to get information across the Atlantic was on a ship. A message sent from London to New York would take at least 10 days to arrive, and a reply would take just as long. A simple business transaction or a piece of breaking news could take nearly a month to cross the ocean and return. The world was vast, and the oceans were a barrier to communication.</p>
<p>The transatlantic telegraph cable changed everything. After a failed attempt in 1858, the first <em>lasting</em> connection was established in 1866. Laying a single, insulated copper wire across more than 3,000 kilometers of the treacherous North Atlantic seabed was one of the greatest engineering feats of the 19th century.</p>
<p>Suddenly, the 10-day journey of a message became a matter of minutes. The world shrank in a way that was previously unimaginable. For the first time, continents could converse in near real-time. News, financial data, and personal messages that once traveled at the speed of a steamship now traveled at the speed of electricity. It was the birth of our global network.</p>
<p>That single wire, painstakingly laid over 150 years ago, was the ancestor of the incredible web of cables that powers our internet today. Instead of one copper wire carrying a few words per minute in Morse code, we now have hundreds of undersea fiber-optic cables carrying terabits of data every second. They are the backbone of our modern world, transmitting everything from video calls to this very article.</p>
<p>To see the stunning evolution from that first transatlantic wire to the dense global network of today, <strong>explore this <a href="https://map.kmcd.dev/" rel="external">interactive map of modern undersea fiber-optic cables</a></strong>. It’s the direct legacy of the dits and dahs that first conquered the ocean. If you&rsquo;ve been following my side projects, you might have seen this link coming.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style=" width:500px;">
<a href="https://map.kmcd.dev" target="_blank">

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/morse/map_hu_53521b0714ede1e0.webp"
             alt="Map of the Internet"/>
    


</a>

    </span>

    
</div>

<hr>
<h2 id="the-enduring-echo-of-dits-and-dahs">The Enduring Echo of Dits and Dahs</h2>
<p>From revolutionizing global communication on telegraph wires to its enduring legacy in maritime and amateur radio, Morse code is far more than a historical artifact. It stands as a powerful testament to human ingenuity. It embodies the fundamental principle that any complex idea, from a simple greeting to a national headline, can be distilled into a simple, universal language.</p>
<p>So while it may no longer be the backbone of our global network, learning Morse code offers a unique connection to the history of technology. It’s a chance to appreciate the rhythm and precision that started it all.</p>
<p>Ready to see how you measure up? Tap into history and test your skills on the <a href="https://morse.kmcd.dev" rel="external">Morse Speed Typer</a>. You might just find a new appreciation for the simple <code>... --- ...</code> that connected the world.</p>
]]></content:encoded></item><item><title>Can You Hack a Phone with Your Voice?</title><link>https://kmcd.dev/posts/can-you-hack-a-phone-with-your-voice/</link><pubDate>Tue, 15 Jul 2025 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/can-you-hack-a-phone-with-your-voice/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/can-you-hack-a-phone-with-your-voice/cover_hu_f5daa6597fe6bacd.webp" /> &lt;/p>
                
                Or How I Got Thousands of Mastodon Users to Whistle at Their Screens
                </description><content:encoded><![CDATA[<p>Thousands of people on Mastodon recently started whistling at their computer screens. I&rsquo;m the one who asked them to do it. No, it wasn&rsquo;t a strange new CAPTCHA, but an experiment in hacking history.</p>
<p>You see, long before the internet became our digital playground, a different breed of tech enthusiast was busy exploring the phone network. These &ldquo;phone phreaks&rdquo; were the original hackers, and one of their most legendary feats was using a simple <a href="https://en.wikipedia.org/wiki/2600_hertz" rel="external"><strong>2600Hz tone</strong></a> to gain control of phone lines in the US.</p>
<p>Now, let&rsquo;s be clear: most phreakers couldn&rsquo;t just whistle this with perfect pitch. That’s the stuff of phreaking legend. In reality, they were a resourceful bunch, using whatever they could get their hands on—instruments, tone generators, and even a toy whistle that famously came in a box of <a href="https://www.thehenryford.org/collections-and-research/digital-collections/artifact/455857/" rel="external"><strong>Cap&rsquo;n Crunch cereal</strong></a>. That little plastic toy just happened to produce a perfect 2600Hz tone, making it an unlikely key to the entire phone system. This discovery helped kick off a whole subculture of people building &ldquo;<strong>blue boxes</strong>&rdquo; and other devices to explore the network&rsquo;s hidden depths. The community&rsquo;s name is immortalized in the legendary <a href="https://www.2600.com/" rel="external"><strong>2600: The Hacker Quarterly</strong></a> magazine and its podcast, <em>Off The Hook</em>.</p>
<p>As a developer with a soft spot for retro-tech, I had to bring this piece of history to life. So, I built the <strong>&ldquo;Phone Phreak Emulator.&rdquo;</strong> It&rsquo;s a simple web app that listens to your whistle and tells you how close you get to that magic frequency. No free calls, sorry, but you do get bragging rights.</p>
<p>Here&rsquo;s what it looks like:</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">
<a href="https://phreak.kmcd.dev" target="_blank">

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/can-you-hack-a-phone-with-your-voice/screenshot_hu_346a0b63ec33317d.webp"
             alt="26000Hz Phreaker emulator" width="600px"/>
    


</a>

    </span>

    
</div>

<p>I tossed a link up <a href="https://infosec.exchange/@sudorandom/114704013454618750" rel="external">on Mastodon</a>, and things got a little wild. The post exploded. Thousands of people started whistling at their screens. It turns out, hitting that perfect tone is a lot harder than it sounds. I, for one, can barely squeak out 1500Hz. My wife, a woodwind player? She nails it. Consistently.</p>
<p>But beyond the fun of watching the stats climb, something even more incredible happened.</p>
<h4 id="how-mastodon-gave-me-a-history-lesson">How Mastodon Gave Me a History Lesson</h4>
<p>The most incredible outcome of this little project was the history lesson I received from the community. I put out a simple app, and what I got back was a <a href="https://infosec.exchange/@sudorandom/114704013454618750" rel="external">masterclass in telecommunications history</a>. The replies were flooded with fascinating details from people who were there, engineers, and fellow enthusiasts. The thread felt like old friends sharing stories and personal anecdotes around a campfire.</p>
<p>I learned about the intricacies of in-band signaling, the difference between the US &ldquo;Ma Bell&rdquo; system and the UK&rsquo;s GPO network, and so much more. This flood of knowledge was too good to ignore. Thanks to the community&rsquo;s wonderful, detailed replies, I discovered their phone network used <a href="http://www.samhallas.co.uk/articles/fiddling_phones_2.htm" rel="external"><strong>2280Hz</strong></a>. As a direct result of that fantastic, crowdsourced history lesson, I updated the app. The Phone Phreak Emulator now includes a &ldquo;UK Mode,&rdquo; allowing you to test your skills against the <strong>2280Hz</strong> tone as well.</p>
<p>It&rsquo;s been a beautiful, chaotic, and wonderfully nerdy experience. It was a whole community coming together to collectively remember a piece of hacking history, all while looking slightly ridiculous as they whistled at their monitors and phones.</p>
<p>So, if you&rsquo;re feeling adventurous and want to see if you have the vocal chops of a legendary phone phreak, give it a shot. Head over to <strong><a href="https://phreak.kmcd.dev/" rel="external">phreak.kmcd.dev</a></strong> and let me know how you do. Just don&rsquo;t blame me if your coworkers start giving you funny looks.</p>
]]></content:encoded></item><item><title>Visualizing the Internet (2025)</title><link>https://kmcd.dev/posts/internet-map-2025/</link><pubDate>Mon, 16 Jun 2025 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/internet-map-2025/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/internet-map-2025/cover_hu_1b09e4230b456bc4.webp" /> &lt;/p>
                
                An all-new interactive map of the Internet, showing the evolution of undersea cables and internet exchanges with year-by-year animation and detailed statistics.
                </description><content:encoded><![CDATA[<p>For the past couple of years, I&rsquo;ve been creating visualizations of the internet&rsquo;s physical infrastructure. This project pieces together data from a few sources, and for me, seeing this data visualized together is compelling. These maps show the undersea fiber optic cables that form the backbone of global connectivity and the Internet Exchange Points (IXPs) where networks meet. This year, I&rsquo;m thrilled to announce a major evolution of the project. Instead of static images and pre-rendered videos, the Internet Map is now a fully interactive, animated map that you can see online.</p>
<p>You can explore the new map live at <a href="https://map.kmcd.dev" rel="external"><strong>map.kmcd.dev</strong></a>.</p>
<p>The new version lets you take control. You can pan, zoom, and step through time from the earliest days of the subsea cable network to the latest deployments in 2025. A new statistics panel provides a detailed snapshot for any given year, offering a richer understanding of how our connected world has grown.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">
<a href="https://map.kmcd.dev" target="_blank">

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/internet-map-2025/screenshot_hu_2b60824cab7d522.webp"
             alt="Map of the Internet"/>
    


</a>

    </span>

    
</div>

<h2 id="what-youre-looking-at">What You&rsquo;re Looking At</h2>
<p>The map visualizes two critical components of the internet&rsquo;s physical layer.</p>
<h3 id="a-note-on-what-youre-seeing-and-not-seeing">A Note on What You&rsquo;re Seeing (and Not Seeing)</h3>
<p>It&rsquo;s important to note that this map visualizes publicly available data, which doesn&rsquo;t capture the full picture of global connectivity. The city peering information, for instance, is sourced from <a href="https://www.peeringdb.com/" rel="external">PeeringDB</a>, which tracks publicly advertised connections at IXPs. A vast amount of internet traffic also flows through private peering arrangements and paid transit links that are not publicly documented and therefore do not appear here.</p>
<p>Similarly, the map focuses on the <em>intercontinental</em> backbone of submarine cables. It does not show the incredibly dense web of terrestrial fiber optic cables that run under our streets and alongside major roads. While that data would be fascinating, visualizing it would be overwhelming, and acquiring a complete dataset is nearly impossible as network providers rarely share this proprietary information.</p>
<h3 id="submarine-cables">Submarine Cables</h3>
<p>The lines snaking across the ocean floors are <a href="https://en.wikipedia.org/wiki/Submarine_communications_cable" rel="external">submarine communications cables</a>. These bundles of fiber optic strands are the high-speed data arteries that connect continents. Laying and maintaining them is a modern marvel of engineering, involving everything from specialized cable-laying ships to underwater robots for repairs. As you explore the map, you can see how the web of these cables has become denser over time, enabling the global, real-time communication we now take for granted. By the start of 2025, the network has grown to <strong>599</strong> cables, spanning a staggering <strong>1,602,092 kilometers</strong>.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/internet-map-2025/alwayshasbeen_hu_50fa9a5aace669cd.webp"
             alt="Map of the Internet"/>
    



    </span>

    
</div>

<h4 id="a-physical-target-vulnerabilities-and-sabotage">A Physical Target: Vulnerabilities and Sabotage</h4>
<p>While these cables are heavily armored, especially in shallower coastal waters where most damage occurs, their isolation on the seabed makes them vulnerable. For decades, the most common threat has been accidental damage from fishing trawlers and dragged anchors. However, in recent years, a more alarming trend has emerged: intentional sabotage. The increasing frequency of suspicious cable cuts suggests that these vital arteries of communication are becoming targets in geopolitical conflicts, a reality that may have brought many new visitors to this map.</p>
<p>Here are just a few of the many recent incidents:</p>
<ul>
<li>
<p><strong><a href="https://jamestown.org/program/strangers-on-a-seabed-sino-russian-collaboration-on-undersea-cable-sabotage-operations/" rel="external">The Balticconnector Pipeline and Cable Damage (October 2023)</a></strong>: The Balticconnector gas pipeline and two telecom cables between Finland and Estonia were damaged by a dragged anchor from the Hong Kong-flagged ship <em>Newnew Polar Bear</em>. China later admitted its vessel was responsible but claimed it was an accident.</p>
</li>
<li>
<p><strong><a href="https://www.cbsnews.com/news/undersea-cables-cut-europe-finland-germany-hint-russia-sabotage/" rel="external">The C-Lion1 and BCS East-West Interlink Cuts (November 2024)</a></strong>: Two key telecom cables in the Baltic Sea, C-Lion1 (Finland-Germany) and BCS East-West Interlink (Lithuania-Sweden), were severed. The Chinese-owned cargo ship <em>Yi Peng 3</em> was the primary suspect after it was observed making anomalous movements in the area.</p>
</li>
<li>
<p><strong><a href="https://apnews.com/article/nato-france-russia-baltic-cables-ships-damage-764964a275530915c2cc5af1125ec125" rel="external">The Christmas Day Baltic Cable Cuts (December 2024)</a></strong>: On Christmas Day 2024, the Estlink 2 power cable and other telecom cables between Finland and Estonia were damaged by a dragged anchor. Finnish authorities seized the suspected vessel, the oil tanker <em>Eagle S</em>, which was identified as part of Russia&rsquo;s &ldquo;shadow fleet&rdquo;.</p>
</li>
<li>
<p><strong><a href="https://www.vice.com/en/article/taiwan-internet-cables-matsu-china/" rel="external">The Matsu Islands Blackout (February 2023)</a></strong>: Two undersea cables connecting Taiwan to its outlying Matsu Islands were severed by Chinese vessels, leaving the 14,000 residents with severely disrupted internet for over 50 days. The incident highlighted the societal impact of such disruptions and was seen as part of a broader pressure campaign by China.</p>
</li>
<li>
<p><strong><a href="https://www.twz.com/news-features/taiwan-coast-guard-blames-chinese-owned-ship-for-cutting-undersea-communications-cable" rel="external">The Trans-Pacific Express Cable Cut (January 2025)</a></strong>: The major Trans-Pacific Express international cable was cut near Taiwan, with suspicion falling on the Chinese-owned cargo ship <em>Shunxin 39</em>. The vessel had a history of using multiple identities to evade tracking and sailed erratically over the cable&rsquo;s location before the incident.</p>
</li>
<li>
<p><strong><a href="https://www.csis.org/analysis/red-sea-cable-damage-reveals-soft-underbelly-global-economy" rel="external">The Red Sea Cable Disruption (February 2024)</a></strong>: Three critical cables in the Red Sea were severed by the anchor of the sinking cargo ship <em>Rubymar</em>, which had been struck by a Houthi missile. The incident disrupted a significant portion of Europe-Asia data traffic and highlighted the vulnerability of infrastructure in contested maritime chokepoints.</p>
</li>
<li>
<p><strong><a href="https://www.cbc.ca/news/canada/nova-scotia/bell-subsea-fibre-optic-cable-newfoundland-1.7461963" rel="external">The Gulf of St. Lawrence Sabotage (December 2023 &amp; 2024)</a></strong>: A subsea cable connecting Nova Scotia and Newfoundland was deliberately cut in December 2023 and again in December 2024. Evidence showed an &ldquo;angle grinder cut&rdquo; through the steel-wrapped cable, confirming sabotage, though the perpetrator and motive remain unknown.</p>
</li>
</ul>
<p>These examples represent only a fraction of such incidents, which have escalated in frequency and impact in recent years.</p>
<h3 id="internet-exchange-points-ixps">Internet Exchange Points (IXPs)</h3>
<p>The circles on the map represent cities with <a href="https://www.cloudflare.com/learning/cdn/glossary/internet-exchange-point-ixp/" rel="external">Internet Exchange Points</a>. If submarine cables are the interstate highways of the internet, then IXPs are the bustling, hyper-connected metropolitan areas where all the traffic is headed.</p>
<p>An IXP is a physical data center, or a set of connected data centers, where many different networks can physically plug into each other to exchange traffic directly. This process is called &ldquo;peering.&rdquo;</p>
<p>So why do hundreds of networks choose to gather in the same buildings in cities like Frankfurt or Amsterdam? The answer is a powerful network effect that you could call digital gravity. The value of an IXP is determined by the networks present there. Once a major network joins, it becomes exponentially more attractive for others to join as well.</p>
<p>The most powerful sources of this gravity are large content providers like Google, Meta, Apple, and Netflix. These companies have an &ldquo;open peering policy.&rdquo; In essence, they are saying to any Internet Service Provider (ISP): &ldquo;Connect with us directly here at the IXP, and we will give your customers a faster, better path to our services.&rdquo;</p>
<p>This creates a powerful win-win scenario:</p>
<ul>
<li>The ISP wins because their customers get lightning-fast, low-latency access to YouTube, Google Drive, or Apple&rsquo;s App Store. This makes the ISP&rsquo;s own service more valuable and competitive.</li>
<li>Google and Apple win because they get to deliver their content without paying high fees to third-party backbone carriers. Every byte of data they serve over a direct peering connection is money saved.</li>
</ul>
<p>This economic incentive is the engine that drives the growth you see on the map. ISPs flock to the IXPs where the big content providers are, which in turn attracts more content providers, creating a feedback loop of ever-increasing capacity and value. This is why a handful of cities have become global hubs with staggering traffic volumes, while others remain smaller, regional nodes.</p>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/e3c2b46861e618198a2c96c00eebc8a6a427f7b09848af9f3606b430f61fd089.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<h2 id="the-world-in-2025-a-snapshot">The World in 2025: A Snapshot</h2>
<p>The animation and data now extend to 2025, revealing significant ongoing investment. In this year alone, <strong>31 new cables</strong> were added (or are promised very soon), stretching over <strong>144,320 kilometers</strong>, enough to circle the earth <em>three and a half times</em>.</p>
<p>Some of the longest and most impactful new cables of 2025 include:</p>
<ul>
<li><strong><a href="https://www.submarinenetworks.com/en/systems/trans-pacific/bifrost" rel="external">Bifrost</a> (19,888 km):</strong> A monumental project directly connecting Singapore to North America via Indonesia, the Philippines, and Guam. It&rsquo;s a joint effort by Meta, Keppel, and Telin to bolster connectivity across the Asia-Pacific region.</li>
<li><strong><a href="https://www.submarinenetworks.com/en/systems/trans-pacific/echo/echo-cable-system-overview" rel="external">Echo</a> (17,184 km):</strong> Another critical trans-Pacific cable, built by Google and Meta, that forges a new, resilient path from the U.S. to Singapore, also landing in Guam and Indonesia. This cable deliberately avoids the crowded northern routes to increase network diversity.</li>
<li><strong><a href="https://www.submarinenetworks.com/en/systems/brazil-us/firmina" rel="external">Firmina</a> (14,517 km):</strong> A Google-led cable enhancing the North-South America connection. It runs from the U.S. East Coast to Argentina, with landings in Brazil and Uruguay, dramatically improving access to Google services in South America.</li>
<li><strong><a href="https://www.submarinenetworks.com/en/systems/trans-pacific/tpu" rel="external">TPU</a> (13,470 km):</strong> A Google-owned cable system connecting the U.S. with Taiwan and the Philippines, bolstering trans-Pacific capacity.</li>
<li><strong><a href="https://www.submarinenetworks.com/en/systems/trans-pacific/juno" rel="external">JUNO</a> (11,710 km):</strong> A cable system by Seren Juno Network connecting Japan to the U.S., utilizing advanced technology to offer a high number of fiber pairs and enhance communication resiliency.</li>
</ul>
<h2 id="regional-peering-powerhouses">Regional Peering Powerhouses</h2>
<p>Looking at the total peering capacity reveals a clear global hierarchy. Europe remains the undisputed leader, with an incredible <strong>1.5 Pbit/s</strong> of capacity. Asia and North America follow with robust networks of their own, while South America shows impressive growth.</p>
<div class="container">
  <pre class="mermaid">pie title Peering Capacity by Region (2025)
    "Europe": 1500
    "Asia": 430
    "North America": 403
    "South America": 313
    "Africa": 67.9
    "Oceania": 67.2
  </pre>
</div>
<p>This massive regional capacity is concentrated in a few key metropolitan hubs. The list of top peering cities shows just how vital they are to the global network:</p>
<ul>
<li><strong>Amsterdam, NL</strong>: 200 Tbit (+12.7 Tbit)</li>
<li><strong>Frankfurt, DE</strong>: 166 Tbit (+8.19 Tbit)</li>
<li><strong>São Paulo, BR</strong>: 157 Tbit (+3.16 Tbit)</li>
<li><strong>London, GB</strong>: 113 Tbit (+3.21 Tbit)</li>
<li><strong>Tokyo, JP</strong>: 90.2 Tbit (+2.18 Tbit)</li>
</ul>
<p>The growth in a city like São Paulo is remarkable and shows the increasing investment in internet infrastructure in South America, directly supported by new cables like Firmina.</p>
<h2 id="how-its-made-the-new-tech-stack">How It&rsquo;s Made: The New Tech Stack</h2>
<p>The transition to an interactive map required a complete overhaul of the technology stack. The previous versions, which relied on generating static SVG images, faced several challenges. It was difficult to dynamically size the lines representing cables and find the right balance of detail for country borders; too much detail slowed the map down, while too little looked simplistic when zoomed in.</p>
<p>The solution was to adopt a tile-based methodology, where map tiles at different levels of detail are fetched dynamically as a user zooms—the same concept used by Google Maps. I was faced with a choice: implement this highly complex tiling logic myself or use a well-supported library. Since the project&rsquo;s focus was on data visualization, I opted for the more direct path by using <a href="https://leafletjs.com/" rel="external">Leaflet</a>, a powerful library for creating dynamic and interactive maps.</p>
<p>The back-end Go scripts that gather and process the data from sources like <a href="https://telegeography.com/" rel="external">TeleGeography</a> and <a href="https://www.peeringdb.com/" rel="external">PeeringDB</a> were largely unchanged, only needing a few new fields in the JSON output to power the new front end.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/internet-map-2025/expand_hu_86baeb38db3e5e34.webp"
             alt="Map of the Internet"/>
    



    </span>

    
</div>

<p>Beyond the core technology, I also wanted to incorporate a few small details to improve the user experience and make the map feel more intuitive and personal:</p>
<ul>
<li><strong>Personalized View</strong>: The map automatically geolocates your region and centers the initial view there. My hope is that the map feels familiar and relevant the moment you open it, no matter where you are in the world.</li>
<li><strong>Persistent &lsquo;About&rsquo; Section</strong>: The &lsquo;About this map&rsquo; panel remembers its state. If you close it, it stays closed on subsequent visits and refreshes until you decide to open it again.</li>
<li><strong>Relative City Sizing</strong>: The size of each city circle is relative to its total peering bandwidth, giving an immediate visual sense of where the major hubs of connectivity are concentrated.</li>
<li><strong>Interactive Highlighting</strong>: Hovering over or clicking on any cable or city highlights it and brings it to the forefront. This small detail makes it much easier to focus on and explore individual parts of the network.</li>
</ul>
<h2 id="closing-thoughts">Closing Thoughts</h2>
<p>This project continues to be a fascinating exploration of the physical reality of our digital world. By making the map interactive, I hope to provide a more powerful tool for anyone curious about the immense and intricate infrastructure that underpins our daily lives. As global data demand soars, the growth of these subsea cables and peering exchanges will only become more critical. Explore the map, watch the internet grow, and see for yourself how the world gets connected.</p>
<p>This project was a significant undertaking, and I&rsquo;ve been thrilled to see it shared in various places online. If you choose to share it, I only ask that you please provide attribution by linking back to the project, just as I give credit to my own data sources, <a href="https://telegeography.com/" rel="external">TeleGeography</a> and <a href="https://www.peeringdb.com/" rel="external">PeeringDB</a>.</p>
<p>Thank you for reading, and please feel free to explore and share the map with others! <a href="https://map.kmcd.dev" rel="external">map.kmcd.dev</a></p>
]]></content:encoded></item><item><title>HTTP QUERY and Go</title><link>https://kmcd.dev/posts/http-query/</link><pubDate>Wed, 04 Jun 2025 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/http-query/</guid><description><![CDATA[ 
                <p> <img hspace="5" src="https://kmcd.dev/posts/http-query/cover_hu_2439f564eb3b6556.webp" /> </p>
                
                We need another HTTP verb. I&#39;ll explain why.
                ]]></description><content:encoded><![CDATA[<p>You&rsquo;re likely familiar with the HTTP methods, <strong>GET</strong> and <strong>POST</strong>, the workhorses of HTTP. These have both worked surprisingly well and have provided well-defined caching behavior for over a quarter of a century. However, neither of these solves the problem of complex request parameters without completely throwing out the caching semantics of <code>GET</code>. This is where a concept like a <strong>QUERY</strong> method comes in, and it&rsquo;s not just a thought experiment; it&rsquo;s an area actively being <a href="https://httpwg.org/http-extensions/draft-ietf-httpbis-safe-method-w-body.html" rel="external">explored by the IETF HTTP Working Group</a>.</p>
<h3 id="but-why">But, why?</h3>
<p>The standard HTTP methods serve us well, except the ones that don&rsquo;t&hellip; but that&rsquo;s a topic for another day. <strong>GET</strong> is great for simple data retrieval, but its reliance on URL parameters makes it cumbersome for complex queries or large sets of input parameters. URLs have practical length limits, and embedding deeply nested structures is awkward.</p>
<p>This often leads developers to use <strong>POST</strong> for what are semantically read-only query operations. Many widely-used protocols effectively do this:</p>
<ul>
<li><strong>GraphQL</strong> typically uses POST requests with JSON bodies to send queries.</li>
<li><strong>gRPC</strong> and <strong>gRPC-Web</strong> typically rely exclusively on POST.</li>
<li>Older protocols like <strong>SOAP</strong> and <strong>XML-RPC</strong> almost exclusively use POST to encapsulate their operations, including data retrieval. This has been an issue for a long time!</li>
</ul>
<p>While using POST works, it&rsquo;s a compromise. POST traditionally implies an action that might change state on the server, which means intermediaries (read: caches) and even client-side logic might treat these &ldquo;query-via-POST&rdquo; requests with undue caution, forgoing caching or automatic retries that would be safe for a truly read-only operation.</p>
<p>This is precisely the problem that a dedicated safe method with a request body aims to solve. The IETF HTTP Working Group is discussing such a method in a draft titled <a href="https://httpwg.org/http-extensions/draft-ietf-httpbis-safe-method-w-body.html" rel="external">&ldquo;A Safe HTTP Method with a Request Content&rdquo;</a>. As the draft states:</p>
<blockquote>
<p>The QUERY method provides a solution that spans the gap between the use of GET and POST. As with POST, the input to the query operation is passed along within the content of the request rather than as part of the request URI. Unlike POST, however, the method is explicitly safe and idempotent, allowing functions like caching and automatic retries to operate.</p>
</blockquote>
<p>While the draft uses &ldquo;QUERY&rdquo; as a candidate name (among others), the core idea is what we&rsquo;re exploring: a method for safe, idempotent data retrieval that can carry a payload. For the rest of this article, we&rsquo;ll continue to use &ldquo;QUERY&rdquo; to represent this concept and show how you can implement such a custom method in Go today.</p>
<p>It&rsquo;s crucial to remember that until such a method is formally standardized and widely adopted, <strong>using custom HTTP methods can impact interoperability</strong>. However, for internal APIs, tightly controlled systems, or as a forward-looking experiment, they can be very useful.</p>
<h3 id="server-side-with-go">Server-Side with Go</h3>
<p>Go 1.22 introduced enhancements to <code>http.ServeMux</code> that allow you to register handlers for specific HTTP methods and paths more directly. Let&rsquo;s build a server that handles our custom <strong>QUERY</strong> method:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">queryHandler</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// The new ServeMux handles method checking based on registration.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	body<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadAll</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		http<span style="color:#eceff4">.</span><span style="color:#88c0d0">Error</span><span style="color:#eceff4">(</span>w<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;Error reading request body&#34;</span><span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>StatusInternalServerError<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> r<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// In a real application, you&#39;d parse the query from the body</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// (e.g., JSON) and fetch data accordingly.</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fprintf</span><span style="color:#eceff4">(</span>w<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;Received your QUERY request with body: %s\n&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>body<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Handled QUERY request with body: %s&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>body<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	mux <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServeMux</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Register handler specifically for QUERY method on /data path</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandleFunc</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;QUERY /data&#34;</span><span style="color:#eceff4">,</span> queryHandler<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Server starting on port 8080, handling custom QUERY method...&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServe</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;:8080&#34;</span><span style="color:#eceff4">,</span> mux<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatal</span><span style="color:#eceff4">(</span>err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>In this server:</p>
<ol>
<li>We use <code>mux.HandleFunc(&quot;QUERY /data&quot;, queryHandler)</code> to directly associate the <code>queryHandler</code> with our custom <strong>QUERY</strong> HTTP method for the <code>/data</code> path.</li>
<li>The <code>queryHandler</code> reads the request body, where the complex query parameters would reside.</li>
</ol>
<p>Congratulations, we&rsquo;ve made an API that uses the QUERY method. Now let&rsquo;s write a client to pair with this server.</p>
<h3 id="client-side-also-with-go">Client-Side, also with Go</h3>
<p>Here&rsquo;s how a Go client can send a <strong>QUERY</strong> request:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;strings&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	queryPayload <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">`{&#34;filters&#34;: {&#34;status&#34;: &#34;active&#34;, &#34;category&#34;: &#34;electronics&#34;}, &#34;fields&#34;: [&#34;name&#34;, &#34;price&#34;]}`</span>
</span></span><span style="display:flex;"><span>	client <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>http<span style="color:#eceff4">.</span>Client<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Create a new request with the custom &#34;QUERY&#34; method</span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewRequest</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;QUERY&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;http://localhost:8080/data&#34;</span><span style="color:#eceff4">,</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>queryPayload<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error creating request: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span><span style="color:#88c0d0">Set</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Content-Type&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;application/json&#34;</span><span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// Important for body processing</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	resp<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> client<span style="color:#eceff4">.</span><span style="color:#88c0d0">Do</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error sending request: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Response Status:&#34;</span><span style="color:#eceff4">,</span> resp<span style="color:#eceff4">.</span>Status<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	body<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadAll</span><span style="color:#eceff4">(</span>resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Error reading response body: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Response Body:&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>body<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>The client uses <code>http.NewRequest(&quot;QUERY&quot;, ...)</code> to specify the custom method and sends the <code>queryPayload</code> in the request body.</p>
<h3 id="client-side-with-curl">Client-Side with cURL</h3>
<p>Although web browsers won&rsquo;t randomly make QUERY calls, many other tools can use QUERY. Many also support arbitrary HTTP verbs, although this isn&rsquo;t typically leveraged due to filters from load balancers, firewalls, etc.</p>
<p>Anyway, here&rsquo;s what that request looks like with cURL:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl -X QUERY -d <span style="color:#a3be8c">&#39;{&#34;filters&#34;: {&#34;status&#34;: &#34;active&#34;, &#34;category&#34;: &#34;electronics&#34;}, &#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">fields&#34;: [&#34;name&#34;, &#34;price&#34;]}&#39;</span> http://localhost:8080/data -v
</span></span><span style="display:flex;"><span>* Host localhost:8080 was resolved.
</span></span><span style="display:flex;"><span>* IPv6: ::1
</span></span><span style="display:flex;"><span>* IPv4: 127.0.0.1
</span></span><span style="display:flex;"><span>*   Trying <span style="color:#81a1c1">[</span>::1<span style="color:#81a1c1">]</span>:8080...
</span></span><span style="display:flex;"><span>* Connected to localhost <span style="color:#81a1c1">(</span>::1<span style="color:#81a1c1">)</span> port <span style="color:#b48ead">8080</span>
</span></span><span style="display:flex;"><span>&gt; QUERY /data HTTP/1.1
</span></span><span style="display:flex;"><span>&gt; Host: localhost:8080
</span></span><span style="display:flex;"><span>&gt; User-Agent: curl/8.7.1
</span></span><span style="display:flex;"><span>&gt; Accept: */*
</span></span><span style="display:flex;"><span>&gt; Content-Length: <span style="color:#b48ead">89</span>
</span></span><span style="display:flex;"><span>&gt; Content-Type: application/x-www-form-urlencoded
</span></span><span style="display:flex;"><span>&gt; 
</span></span><span style="display:flex;"><span>* upload completely sent off: <span style="color:#b48ead">89</span> bytes
</span></span><span style="display:flex;"><span>&lt; HTTP/1.1 <span style="color:#b48ead">200</span> OK
</span></span><span style="display:flex;"><span>&lt; Date: Sat, <span style="color:#b48ead">31</span> May <span style="color:#b48ead">2025</span> 15:47:45 GMT
</span></span><span style="display:flex;"><span>&lt; Content-Length: <span style="color:#b48ead">129</span>
</span></span><span style="display:flex;"><span>&lt; Content-Type: text/plain<span style="color:#eceff4">;</span> charset<span style="color:#81a1c1">=</span>utf-8
</span></span><span style="display:flex;"><span>&lt; 
</span></span><span style="display:flex;"><span>Received your QUERY request with body: <span style="color:#81a1c1">{</span><span style="color:#a3be8c">&#34;filters&#34;</span>: <span style="color:#81a1c1">{</span><span style="color:#a3be8c">&#34;status&#34;</span>: <span style="color:#a3be8c">&#34;active&#34;</span>, <span style="color:#a3be8c">&#34;category&#34;</span>: <span style="color:#a3be8c">&#34;electronics&#34;</span><span style="color:#81a1c1">}</span>, <span style="color:#a3be8c">&#34;fields&#34;</span>: <span style="color:#81a1c1">[</span><span style="color:#a3be8c">&#34;name&#34;</span>, <span style="color:#a3be8c">&#34;price&#34;</span><span style="color:#81a1c1">]}</span>
</span></span><span style="display:flex;"><span>* Connection <span style="color:#616e87;font-style:italic">#0 to host localhost left intact</span>
</span></span></code></pre></div><h3 id="query-vs-get-and-post---a-clearer-separation">QUERY vs. GET and POST - A Clearer Separation</h3>
<p>Let&rsquo;s summarize the distinctions with our (potentially future-standard) QUERY method in mind:</p>
<ul>
<li><strong>GET</strong>: Used for retrieving data. Parameters are typically sent in the URL. GET requests <em>must</em> be safe and idempotent. They generally don&rsquo;t have a body.</li>
<li><strong>POST</strong>: Used for submitting data to be processed, often resulting in a change of state or side effects on the server (e.g., creating a new resource). Parameters are sent in the request body. Not necessarily safe or idempotent.</li>
<li><strong>QUERY (custom/proposed)</strong>: Used to <em>request data</em> (like GET) but with the ability to send a <em>complex query in the request body</em> (like POST). Crucially, it is defined as <strong>safe and idempotent</strong> (like GET). This explicitly tells intermediaries and clients that the request has no side effects and can be cached or retried automatically.</li>
</ul>
<h3 id="caching-behavior-get-post-and-query">Caching Behavior: GET, POST, and QUERY</h3>
<p>HTTP caching is vital for performance. A method&rsquo;s characteristics (especially safety and idempotency) directly influence its cacheability.</p>
<h4 id="1-get">1. GET</h4>
<ul>
<li><strong>Behavior</strong>: GET requests are inherently cacheable. Caches readily store and serve responses to GET requests if caching headers (like <code>Cache-Control</code>, <code>Expires</code>, <code>ETag</code>) allow. This is a foundational aspect of HTTP performance.</li>
</ul>
<h4 id="2-post">2. POST</h4>
<ul>
<li><strong>Behavior</strong>: POST requests are generally <em>not</em> cacheable by default. Since POST can have side effects, caching responses could lead to unintended consequences or stale data if the action isn&rsquo;t repeated.</li>
</ul>
<h4 id="3-query">3. QUERY</h4>
<p>The IETF draft emphasizes that a method like QUERY is &ldquo;explicitly safe and idempotent, allowing functions like caching and automatic retries to operate.&rdquo; This is key.</p>
<ul>
<li><strong>Behavior (Current Custom Method)</strong>: Because our <strong>QUERY</strong> method is non-standard <em>today</em>, caches will <strong>not cache it by default</strong>. They typically only automatically consider standard safe methods like GET.
To make responses to a custom QUERY request cacheable:
<ul>
<li>The <strong>server must send explicit caching headers</strong> (e.g., <code>Cache-Control: public, max-age=3600</code>). These headers signal the cacheability of the response.</li>
<li>Intermediary caches (CDNs, reverse proxies) might need <strong>specific configuration</strong> to recognize and cache responses for this custom method.</li>
<li>The <code>Vary</code> HTTP header is important if the response depends on the request body content.</li>
</ul>
</li>
<li><strong>Behavior (Future Standardized Method)</strong>: If a method like QUERY becomes a recognized HTTP standard, caches would likely treat it similarly to GET for caching purposes, provided it&rsquo;s implemented according to its safe and idempotent semantics. This would be a major advantage, allowing complex queries with bodies to be cached as effectively as GET requests.</li>
</ul>
<h3 id="key-takeaway-for-query-caching">Key Takeaway for QUERY Caching</h3>
<p>The <em>intent</em> of a QUERY method is to be cacheable. If using a custom method today, you must provide explicit caching directives. The ongoing standardization effort aims to make this caching behavior more automatic and universally understood, unlocking performance benefits for complex, body-inclusive queries that are currently often forced into less cache-friendly POST requests. How long will this process take? Who knows! These kinds of changes can take decades.</p>
<h2 id="whats-next">What&rsquo;s Next?</h2>
<p>The discussion around a safe HTTP method with a request body is an exciting development. By understanding its purpose and experimenting with custom methods like QUERY in Go, we can better appreciate the nuances of HTTP and prepare for potential future standards that will make our APIs more robust and performant.</p>
<p>Some projects have already taken to adding support for the QUERY method. Take a look at <a href="https://github.com/nodejs/node/issues/51562" rel="external">NodeJS: Support for &lsquo;QUERY&rsquo; method</a>, which added support last year. As demonstrated earlier in this article, using <code>QUERY</code> is already possible in Go. This general support is often true for other languages and libraries as well, since custom HTTP methods are a feature of the HTTP specification.</p>
<p>The other aspect of moving this forward is a bit more nebulous: bureaucracy. There is still work and review to be done to graduate the draft into an official RFC from the IETF. But fear not. There&rsquo;s actually steady changes being made to the draft document. You can tell this from the <a href="https://github.com/httpwg/http-extensions/commits/main/draft-ietf-httpbis-safe-method-w-body.xml" rel="external">git history</a> on the httpwg&rsquo;s repo. At the time of writing May 19th was when the last change was made, so I&rsquo;m certain that this hasn&rsquo;t been forgotten about.</p>
]]></content:encoded></item><item><title>I made a daily word game</title><link>https://kmcd.dev/posts/wordseq/</link><pubDate>Wed, 14 May 2025 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/wordseq/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/wordseq/cover_hu_431a8b94add23f27.webp" /> &lt;/p>
                
                Taking a peek under the covers of making a daily puzzle game
                </description><content:encoded><![CDATA[<p>Ever stare at a word so long it stops looking like a word? Like naming a variable <code>data</code> for the 8th time and suddenly wondering what <code>data</code> even means?</p>
<p>That effect is called <a href="https://en.wikipedia.org/wiki/Semantic_satiation" rel="external">semantic satiation</a>. I made a game that dives headfirst into the theme.</p>
<p>In <code>wordseq</code>, you swap letters to form words, but the deeper you go, the more the grid feels like a linguistic fever dream. One moment you&rsquo;re proud to find &ldquo;plop&rdquo;, the next you&rsquo;re doubting if &ldquo;plop&rdquo; was ever a real word or just a sound effect from a comic book. You win by dragging yourself back to meaning. Enjoy the victory while you can, because there&rsquo;s a new puzzle tomorrow.</p>
<p>I&rsquo;m excited to share a look behind the scenes of my new daily word puzzle game, <code>wordseq</code>. If you haven&rsquo;t tried it yet, you can <a href="https://wordseq.com" rel="external">play the latest puzzle here!</a>. <code>wordseq</code> is a game where you swap adjacent letters to form new words, aiming to find the longest possible sequence. Here&rsquo;s what it looks like to play the game (by a super unrealistically fast player).</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">

    
    
        
        
        <img src="https://kmcd.dev/posts/wordseq/gameplay.gif"
             alt="wordseq gameplay" class="center"/>
    



    </span>

    
</div>

<p>One of the most challenging and rewarding aspects of building <code>wordseq</code> was developing the system that generates the daily puzzles. My goal was to create levels that are not just solvable, but also consistently engaging, fair, and offer that satisfying &ldquo;aha!&rdquo; moment where the answer you&rsquo;ve been looking for hits you in the face. Conversely, I want to avoid the &ldquo;Huh, is this actually the solution?&rdquo; moments.</p>
<p>In this post, we&rsquo;ll explore the intricate process of generating these daily puzzles. We&rsquo;ll cover everything from initial grid creation and word validation to ensuring puzzles are solvable and use interesting vocabulary, leveraging techniques like concurrency in Go and smart dictionary management.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">
<a href="http://wordseq.com" target="_blank">
    
    
        
        
        <img src="https://kmcd.dev/posts/wordseq/wordseq.svg"
             alt="wordseq logo" class="center" height="150px"/>
    

</a>

    </span>

    
</div>

<h2 id="core-gameplay-mechanics">Core Gameplay Mechanics</h2>
<p>Before we dive into generation, here&rsquo;s a quick overview of how to play <code>wordseq</code>:</p>
<ul>
<li><strong>The Core Mechanic:</strong> Players swap any two letters that are directly next to each other (horizontally or vertically).</li>
<li><strong>Word Formation:</strong> Each swap <em>must</em> result in at least one new word spanning the length of a row (this length can vary by difficulty). This new word is formed by the entire row or column where the swap occurred.</li>
<li><strong>The Goal:</strong> The objective is to find the longest possible sequence of valid words by making these swaps. This is what we call the &ldquo;optimal path.&rdquo;</li>
<li><strong>Difficulty Levels:</strong> <code>wordseq</code> offers &rsquo;normal&rsquo;, &lsquo;hard&rsquo;, and &lsquo;impossible&rsquo; modes, which influence parameters like grid size, word length, and the complexity of the solution paths the generator aims for.</li>
</ul>
<p>To better understand how these swaps lead to word formation, let&rsquo;s look at the visual feedback provided to the player during gameplay.</p>
<h3 id="movement">Movement</h3>
<p>Here is what the movement looks like.</p>
<p>Bad move, results in no new words. Don&rsquo;t you like the little wiggle? It&rsquo;s the small things that really shape the experience:
<div class="diagram-wrapper">
    <span class="diagram"
        style="">

    
    
        
        
        <img src="https://kmcd.dev/posts/wordseq/bad-move.gif"
             alt="letters wiggling and turning red" class="center" width="400px"/>
    



    </span>

    
</div>
</p>
<p>Non-optimal move, a move that sets you up to fail. You may think that you made a clever move and discovered a word just slightly askew, but there are many times where there&rsquo;s another word that you need to find first to best set up the longest sequence. You will be greeted by a not-as-scary orange color. Usually you will want to undo your move and look for another:
<div class="diagram-wrapper">
    <span class="diagram"
        style="">

    
    
        
        
        <img src="https://kmcd.dev/posts/wordseq/non-optimal-move.gif"
             alt="letters swapping and a newly completed word turning orange" class="center" width="400px"/>
    



    </span>

    
</div>
</p>
<p>And, of course, the optimal move, the move that finds a new word but also sets you up for the most followup words.
<div class="diagram-wrapper">
    <span class="diagram"
        style="">

    
    
        
        
        <img src="https://kmcd.dev/posts/wordseq/optimal-move.gif"
             alt="letters swapping and a newly completed word turning green" class="center" width="400px"/>
    



    </span>

    
</div>
</p>
<p>The challenge with this game isn&rsquo;t only finding/completing words, but also setting yourself up for the next one. Don&rsquo;t worry, there&rsquo;s no downside for finding the &lsquo;wrong&rsquo; paths.</p>
<h2 id="the-tech-stack">The Tech Stack</h2>
<p>The level generation is a computationally intensive process, so I opted to build the generation script in <a href="https://go.dev/" rel="external"><strong>Go</strong></a>, primarily for its excellent performance and built-in support for concurrency, but also it&rsquo;s the language that I know best right now. This script outputs JSON data for each level. There will be more on the level format later.</p>
<p>The frontend, where you play <code>wordseq</code>, is built with:</p>
<ul>
<li><a href="https://react.dev/" rel="external"><strong>React</strong></a> (using Vite for a fast development experience)</li>
<li><a href="https://www.typescriptlang.org/" rel="external"><strong>TypeScript</strong></a> for type safety</li>
<li><a href="https://tailwindcss.com/" rel="external"><strong>Tailwind CSS</strong></a> for styling</li>
</ul>
<p>The JSON level data from the Go script is fetched by the React frontend to power each day&rsquo;s puzzle.</p>
<h2 id="generating-engaging-levels">Generating Engaging Levels</h2>
<p>Crafting a &ldquo;good&rdquo; <code>wordseq</code> puzzle involves several steps and considerations. We want puzzles that aren&rsquo;t too easy, don&rsquo;t rely on super obscure words, and have a satisfying solution depth. Here&rsquo;s how the generator tackles this:</p>
<h3 id="step-1-initial-grid-generation">Step 1: Initial Grid Generation</h3>
<ul>
<li>
<p><strong>It starts with Randomness:</strong> The process starts by generating a grid (e.g., 5x5) filled with random letters. However, just picking letters completely at random can lead to grids full of very rare letters (like Q, Z, X, J) which makes forming words difficult or leads to trivial &ldquo;write-offs&rdquo; where those letters are ignored. To combat this, the generator uses <strong>approximate letter frequency</strong> (based on English language letter usage) to populate the grid. This creates a more balanced and natural distribution of letters, similar to what you might find in a Scrabble bag.</p>
</li>
<li>
<p><strong>Initial Validation:</strong> Before any moves are possible, the generator verifies that the starting grid contains no valid words matching the puzzle&rsquo;s target length. This ensures the puzzle begins only with the player&rsquo;s first action and avoids potential confusion where the initial state might seem like part of the solution, or where swapping back to the original layout could be misinterpreted as finding a word.</p>
</li>
</ul>
<p>This check involves looking for rows in each row and column, like this:</p>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/2073fb2c6d0f2015f55885479f4addb804f03b9bbe824b65ee394a60c80d791a.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<p>It is important to note that words spent going upwards and to the left aren&rsquo;t seen as words. The game would be far too complex if that were the case.</p>
<h3 id="step-2-building-the-exploration-tree---finding-all-possibilities">Step 2: Building the Exploration Tree - Finding All Possibilities</h3>
<p>This is where the magic (and a lot of computation) happens. For a given initial grid that passes validation, the generator explores all possible valid game sequences:</p>
<ul>
<li><strong>Iteration:</strong> From the current grid state, it identifies every possible adjacent letter swap.</li>
</ul>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/d2eb3da16632feb475a4e3b752a7d11d861182771bca62edf337585bae30f3dd.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<ul>
<li><strong>Validating Swaps (The Big Dictionary):</strong> For each potential swap, it temporarily performs the swap and then checks if the new grid configuration forms at least one new word that spans the length of the grid (horizontally or vertically). This check uses a <strong>large, comprehensive dictionary</strong>. The reason for this large dictionary is critical for player experience: if a player sees a word on the grid and forms it, the game <em>must</em> recognize it, even if it&rsquo;s a bit uncommon. It&rsquo;s incredibly frustrating for a game to not accept a word you know is valid!</li>
</ul>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/6a3964efe7ea22dbc50d63f3bda93b1a9fb4ad6350963e3b4c39b87403a9a8d1.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<ul>
<li><strong>Recursive Exploration:</strong> If a swap is valid (i.e., forms at least one new word), this new grid state and the move that led to it become a new &ldquo;node&rdquo; in an exploration tree. The process then recursively explores all valid swaps from <em>this</em> new state, and so on. This continues until no more valid moves can be made from a state, or a predefined maximum search depth is reached.</li>
</ul>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/ae8e11acf12f331cdab8922e4af690f46fdccba1b687473cf07b7f41e8f77e32.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<h3 id="step-3-analyzing-the-tree">Step 3: Analyzing the Tree</h3>
<p>Once an exploration tree (or a significant portion of it) is built for an initial grid:</p>
<ul>
<li><strong>Calculating <code>maxDepthReached</code>:</strong> For every node in the tree (each representing a game state reached by a valid move), the generator calculates the <code>maxDepthReached</code>. This value signifies the length of the longest possible sequence of <em>further</em> valid moves that can be made starting from that particular node.</li>
<li><strong>Determining Overall Puzzle Depth:</strong> The overall &ldquo;solution length&rdquo; for the puzzle (which the frontend knows as <code>gameData.maxDepthReached</code>) is the highest <code>maxDepthReached</code> value found among the <em>initial</em> possible moves from the starting grid. This represents the longest chain of words the player can achieve.</li>
<li><strong>Filtering Puzzles by Length:</strong> We use <code>RequiredMinTurns</code> and <code>RequiredMaxTurns</code> (configurable parameters for the generator) to filter these potential puzzles. Grids that lead to solutions that are too short (not challenging enough) or excessively long (potentially too tedious) are discarded.</li>
</ul>
<h3 id="step-4-word-dictionaries">Step 4: Word Dictionaries</h3>
<p>This is where the two-dictionary approach comes into play:</p>
<ul>
<li><strong>The Large Dictionary (for Gameplay Logic):</strong> As mentioned, this is used during the tree exploration step to ensure any valid word a player might form is recognized. At the time of writing, the large dictionary has 129,493 words in it.</li>
<li><strong>The Smaller, Curated Dictionary (for Puzzle Quality):</strong> After a potential puzzle is generated, the generator collects <em>all unique words</em> that appear in <em>any</em> valid path within that entire tree. These words are then checked against this smaller, more &ldquo;usable&rdquo; or common dictionary. If the given puzzle uses words that are too obscure or too obscene then the puzzle is discarded entirely. <em>Many</em> potential puzzles are discarded from this check. This is a key step to ensure the final puzzles feel fair and use vocabulary that players are likely to know or find satisfying to discover. At the time of writing, this dictionary has 6,956 words - an absolutely tiny amount compared to the large one.</li>
<li><strong>Controlling Word Variety:</strong> Another filter is <code>MaxUniqueWords</code>. If a potential puzzle contains too many distinct words across all its possible solution paths, it might be too chaotic. This parameter helps keep the word set focused.</li>
</ul>
<h3 id="step-5-concurrency">Step 5: Concurrency</h3>
<p>Generating a single grid, building its exploration tree, and then validating it against all these criteria can take time. To find enough high-quality puzzles for daily release, I needed to process <em>many</em> initial random grids. Doing this sequentially was quite slow, so I ended up splitting up the work by using go routines. This greatly improved the throughput. It&rsquo;s honestly so nice to have a highly parallelizable, CPU-bound problem to work with. It&rsquo;s a nice break from the world of the web where I/O is typically the bottleneck.</p>
<h3 id="step-6-outputting-the-puzzle-data">Step 6: Outputting the Puzzle Data</h3>
<p>Once a grid and its exploration tree pass all the filters, it&rsquo;s deemed a &ldquo;good&rdquo; puzzle. The generator then outputs JSON with the details about a puzzle. To illustrate this, consider a simplified example with a single possible move for illustration purposes:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;initialGrid&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">[</span><span style="color:#a3be8c">&#34;i&#34;</span><span style="color:#eceff4">,</span><span style="color:#a3be8c">&#34;s&#34;</span><span style="color:#eceff4">,</span><span style="color:#a3be8c">&#34;w&#34;</span><span style="color:#eceff4">,</span><span style="color:#a3be8c">&#34;y&#34;</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">[</span><span style="color:#a3be8c">&#34;e&#34;</span><span style="color:#eceff4">,</span><span style="color:#a3be8c">&#34;v&#34;</span><span style="color:#eceff4">,</span><span style="color:#a3be8c">&#34;a&#34;</span><span style="color:#eceff4">,</span><span style="color:#a3be8c">&#34;p&#34;</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">[</span><span style="color:#a3be8c">&#34;r&#34;</span><span style="color:#eceff4">,</span><span style="color:#a3be8c">&#34;a&#34;</span><span style="color:#eceff4">,</span><span style="color:#a3be8c">&#34;b&#34;</span><span style="color:#eceff4">,</span><span style="color:#a3be8c">&#34;a&#34;</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">[</span><span style="color:#a3be8c">&#34;h&#34;</span><span style="color:#eceff4">,</span><span style="color:#a3be8c">&#34;l&#34;</span><span style="color:#eceff4">,</span><span style="color:#a3be8c">&#34;l&#34;</span><span style="color:#eceff4">,</span><span style="color:#a3be8c">&#34;a&#34;</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;wordLength&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">4</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;maxDepthReached&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;explorationTree&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;move&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1">&#34;from&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1">&#34;to&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>      <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;wordsFormed&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span><span style="color:#a3be8c">&#34;seal&#34;</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;maxDepthReached&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;nextMoves&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><ul>
<li><code>initialGrid</code>: The starting letter configuration.</li>
<li><code>wordLength</code>: The target word length for this puzzle.</li>
<li><code>maxDepthReached</code>: The length of the optimal solution path(s).</li>
<li><code>explorationTree</code>: The full tree structure, containing all valid moves, the words they form, and the <code>maxDepthReached</code> from each node.</li>
</ul>
<p>This JSON file is then what the React frontend loads each day to power the game you play. This is what an extremely simple grid looks like, with only one valid move from the initial state.</p>
<h2 id="lessons-learned">Lessons Learned</h2>
<ul>
<li><strong>Dictionaries are Key:</strong> The quality and scope of your dictionaries profoundly impact both gameplay fairness and puzzle quality. The two-dictionary system was vital.</li>
<li><strong>Iterative Refinement:</strong> Level generation isn&rsquo;t something you get right on the first try. It requires constant tweaking of parameters, testing, and playing the generated levels yourself.</li>
<li><strong>Concurrency is Your Friend:</strong> For computationally heavy tasks like this, leveraging concurrency (like Go offers) is almost essential for practical generation times.</li>
<li><strong>Define &ldquo;Good&rdquo; as early as you can:</strong> Having clear criteria for what constitutes a good puzzle (solvable, right length, good words) helps guide the entire generation logic. I was lucky to have my wife, who&rsquo;s a daily word game solver as a test user. Her feedback was (and is) invaluable. Making a game is an iterative process. A lot of times you legitimately don&rsquo;t know if the game you&rsquo;re making is fun. Who knows, maybe I made a game only my wife loves. And honestly? If she’s the only one who loves it, that still feels like a win. (But I do hope you&rsquo;ll enjoy it too.)</li>
<li><strong>Tools to help testing are worth it:</strong> I didn&rsquo;t mention this yet, but I have some tooling now to make testing a lot easier. Not only do I get clear display of the solutions (cheap mode!) but I can push a button and have the game played for me. This has helped a lot when I needed to get to certain game states quickly.</li>
<li><strong>Data structures and Game Design:</strong> I really love how this game relies heavily on computer science fundamentals and data structures. At the end of the day this is just a non-obvious tree traversal with a UI on top of it.</li>
</ul>
<h2 id="whats-next-for-wordseqs-puzzles">What&rsquo;s Next for wordseq&rsquo;s Puzzles?</h2>
<p>I think there&rsquo;s a lot of additional things I can use to judge the difficulty of a given grid. I could judge how often completed words switch rows for the next word, or (if possible), how often it alternates from rows to columns. Often, the puzzle will be easier when it&rsquo;s only one row that is constantly updating. Changing one letter of an existing word is a lot easier for a mind to handle than re-evaluating the whole board every turn.</p>
<p>There&rsquo;s always more refinement that can be done with the dictionary. The small dictionary’s pretty solid now, though an occasional spicy word still sneaks through. Oops.</p>
<p>On the frontend side, I feel like I&rsquo;m getting close to a decent interface. I want to build an &ldquo;infinite mode&rdquo; where you can play random levels (identified by an ID so you can link them and go back to those puzzles later). I actually think this wouldn&rsquo;t be too crazy with the way the components are laid out now.</p>
<p>I&rsquo;m also contemplating how hard it might be to implement a Danish version as I am learning Danish.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Building the level generator for <code>wordseq</code> has been a fascinating journey into algorithms, data structures, and the subtle art of puzzle design. It&rsquo;s a complex system, but seeing it produce fun and challenging puzzles each day is incredibly rewarding.</p>
<p>I hope this peek behind the curtain was interesting!</p>
<ul>
<li><strong>Play wordseq daily:</strong> <a href="https://wordseq.com" title="wordseq" rel="external">wordseq.com</a></li>
<li>I&rsquo;d love to hear your <strong>feedback</strong> on the puzzles or any thoughts you have on level generation. Drop a comment below or find me on <a href="https://bsky.app/profile/kmcd.dev" title="kmcd.dev on bluesky" rel="external">Blue Sky</a> or <a href="https://infosec.exchange/@sudorandom" title="@sudorandom on infosec.exchange, mastodon" rel="external">Mastodon</a>!</li>
</ul>
<p>Thanks for reading!</p>
]]></content:encoded></item><item><title>Protovalidate: Can Input Validation Be This Easy?</title><link>https://kmcd.dev/posts/protovalidate/</link><pubDate>Tue, 11 Feb 2025 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/protovalidate/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/protovalidate/cover_hu_7516b8e4ea8158ed.webp" /> &lt;/p>
                
                Effortless input validation for Protobuf! Protovalidate lets you define rules directly in your .proto files.
                </description><content:encoded><![CDATA[<blockquote>
<p>Since writing this article, ECMAScript support for protovalidate was created and has had its first release with a new library called <a href="https://github.com/bufbuild/protovalidate-es" rel="external">protovalidate-es</a>! Finally, an easy way to share validation rules across backend, frontend and to all kinds of other clients and message-based systems is here.</p>
</blockquote>
<p>User input can be absolute garbage. I can&rsquo;t be trusted to type my name correctly half of the time, so it&rsquo;s obvious that we need to validate input to catch the most obvious mistakes. Zip codes don&rsquo;t include letters, number of pets can&rsquo;t be negative and Hitachi Rail Italy Driverless Metro is not a model of car.</p>
<p>Input validation is a consistent problem in the web services industry. Usually there are constraints that are defined at multiple levels:</p>
<ul>
<li>Databases often have constraints. For example, the size of a single field is usually constrained in some way and there are usually types that offer some guarantees.</li>
<li>Statically typed programming languages have some amount of safety for input. You know an integer type isn&rsquo;t going to magically become a string based on user input.</li>
<li>There are many libraries that assist with this, but backend programmers will have to add constraints on input from users. Usernames can&rsquo;t be 1000 characters long, someone can&rsquo;t be aged negative 10, etc. <strong>This validation is often the most important for data integrity</strong> because it&rsquo;s the last part of the architecture where developers have 100% control.</li>
<li>On the frontend, many of the constraints and rules from the backend are often replicated. This is usually done for UX (User eXperience) reasons. You don&rsquo;t want to wait until you submit a large form with many inputs before realizing you messed up. It&rsquo;s often way better to highlight issues before users try to submit. Often, these constraints are communicated by hand-written documentation that is often not kept up-to-date or not at all, forcing frontend developers to duplicate backend validation logic.</li>
</ul>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/cfd266b27a484519aa89eca2a923e0c92f06256c4120403c06f94b282ed21bd4.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<p>This is where <a href="https://github.com/bufbuild/protovalidate" rel="external">protovalidate</a> comes in. Protovalidate allows you to specify constraints beside your protobuf-defined API and type definitions. I <a href="https://kmcd.dev/posts/api-contracts/" rel="external">talked before about API contracts</a>, but protovalidate goes a step above what protobuf offers by default. In addition to the cross-language type safety that protobuf offers, protovalidate allows you to define additional constraints for each field. So now the answer to many input validation questions can be answered by directly looking at this file or by using a protovalidate library written for several languages. Now both the frontend and backend logic can be powered by the same declarative schema.</p>
<div class="d2-diagram-wrapper">
    <div class="d2-diagram"
        style="display: block; width: 100%; max-width: 100%; margin-left: auto; margin-right: auto;max-height: 80vh;"><img src="/d2-diagrams/423e949c06a34bee418b554cb6749abb3d18929cfee1a5122ddb58b0a250611f.svg" alt="D2 Diagram" loading="lazy" style="max-width: 100%; max-height: inherit; width: 100%; height: auto; object-fit: contain; display: block; margin: 0 auto;" /></div>
</div>
<h2 id="why-protovalidate">Why protovalidate?</h2>
<p>The traditional approach to input validation is often a fragmented and inconsistent mess. Frontend and backend teams may implement their own validation rules, leading to duplicated code, discrepancies, and the constant struggle to keep everything in sync. Imagine the headache of trying to track down and fix a validation bug that exists in three different places! Protovalidate offers a much better way. It provides a centralized, declarative approach, allowing you to define your validation rules directly in your <code>.proto</code> files – the same place you define your data structures. For example, specifying that a username must be between 3 and 50 characters is as simple as adding a few annotations to your Protobuf definition: <code>string name = 1 [(buf.validate.field).string.min_len = 3, (buf.validate.field).string.max_len = 50];</code>. This not only significantly reduces boilerplate code but also ensures consistency across your entire application. Because the rules are defined alongside your data structures, they become a living part of the API contract, improving communication between teams and reducing the risk of misinterpretations.</p>
<p>Protovalidate supports multiple languages, including <a href="https://github.com/bufbuild/protovalidate-go" rel="external">Go</a>, <a href="https://github.com/bufbuild/protovalidate-java" rel="external">Java</a>, <a href="https://github.com/bufbuild/protovalidate-python/" rel="external">Python</a>, and <a href="https://github.com/bufbuild/protovalidate-cc" rel="external">C++</a>, with community support for others like <a href="https://github.com/telus-oss/protovalidate-net" rel="external">.NET</a>. Built on top of the Common Expression Language (CEL), Protovalidate offers a powerful and extensible way to define even the most complex validation rules, ensuring your application&rsquo;s data remains clean and reliable.</p>
<h2 id="showing-it-off">Showing it off</h2>
<p>I can tell you how cool protovalidate is, but I figured it would be better to show you. Let&rsquo;s look at some of the constraints.</p>
<h3 id="built-in-constraints">Built-in constraints</h3>
<p>Protovalidate comes with a set of built-in constraints for common data types. Let&rsquo;s explore some of these, starting with strings.</p>
<h4 id="strings">Strings</h4>
<p>Let&rsquo;s start simple. Here&rsquo;s some constraints that you might see for a username. It has a minimum length of 3 characters and a max length of 50. And this is a required field:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span> <span style="color:#eceff4">[</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#eceff4">(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1">string</span> <span style="color:#81a1c1">=</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    min_len<span style="color:#81a1c1">:</span> <span style="color:#b48ead">3</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    max_len<span style="color:#81a1c1">:</span> <span style="color:#b48ead">50</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#eceff4">},</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#eceff4">(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1;font-weight:bold">required</span> <span style="color:#81a1c1">=</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>Email addresses can also be validated:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1">string</span> email <span style="color:#81a1c1">=</span> <span style="color:#b48ead">3</span> <span style="color:#eceff4">[(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1">string</span><span style="color:#81a1c1">.</span>email <span style="color:#81a1c1">=</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>You can use regex to have precisely describe what a valid value looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// City of residence, must only contain letters, spaces, and hyphens.
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span><span style="color:#81a1c1">string</span> city <span style="color:#81a1c1">=</span> <span style="color:#b48ead">5</span> <span style="color:#eceff4">[(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1">string</span><span style="color:#81a1c1">.</span>pattern <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;^[a-zA-Z]+(?:[\\s-][a-zA-Z]+)*$&#34;</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span></code></pre></div><h4 id="integers">Integers</h4>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1">int32</span> age <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span> <span style="color:#eceff4">[(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1">int32</span><span style="color:#81a1c1">.</span>gte <span style="color:#81a1c1">=</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#eceff4">(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1">int32</span><span style="color:#81a1c1">.</span>lte <span style="color:#81a1c1">=</span> <span style="color:#b48ead">150</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span></code></pre></div><h4 id="enums">Enums</h4>
<p>One potential issue with Protobuf enums is that they don&rsquo;t inherently enforce validation against defined values. For example for an enum defined like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">enum</span> Status <span style="color:#eceff4">{</span> UNKNOWN <span style="color:#81a1c1">=</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">;</span> ACTIVE <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span> <span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>Without explicit validation, a server could receive an undefined enum value (e.g., <code>420</code>) and potentially misinterpret it. To fix this glaring issue, protovalidate has a constraint to limit enums to known and defined values only. Let&rsquo;s look at a field that has this constraint:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>Status status <span style="color:#81a1c1">=</span> <span style="color:#b48ead">4</span> <span style="color:#eceff4">[(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span>enum.defined_only <span style="color:#81a1c1">=</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>With a single annotation you just removed a whole class of issues and simplified input validation.</p>
<h4 id="repeated-fields">Repeated Fields</h4>
<p>There are also constraints for repeated fields. You can constrain the number of items in a list. You can force the items to be unique. You can even add constraints for each item in the list as well.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">repeated</span> <span style="color:#81a1c1">string</span> tags <span style="color:#81a1c1">=</span> <span style="color:#b48ead">7</span> <span style="color:#eceff4">[</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#eceff4">(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1;font-weight:bold">repeated</span><span style="color:#81a1c1">.</span>min_items <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">,</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#eceff4">(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1;font-weight:bold">repeated</span><span style="color:#81a1c1">.</span>max_items <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">,</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#eceff4">(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1;font-weight:bold">repeated</span><span style="color:#81a1c1">.</span>unique <span style="color:#81a1c1">=</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#eceff4">(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1;font-weight:bold">repeated</span><span style="color:#81a1c1">.</span>items.string.min_len <span style="color:#81a1c1">=</span> <span style="color:#b48ead">4</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span></code></pre></div><h3 id="cel-powered-custom-constraints">CEL-powered custom constraints</h3>
<p>One thing that sets protovalidate apart from other validation libraries is the extendability. Protovalidate is built on top of <a href="https://cel.dev/" rel="external">CEL</a>, which is an embeddable expression engine. What this means for protovalidate is that you can write your own constraints. All of the examples above constrain you on a single field, but it can be incredibly powerful to use multiple fields. This can be useful for many situations but in particular it is useful when you have a defined start and end range where the start strictly needs to come before the end. So let&rsquo;s take a look at what that looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">Event</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">int64</span> start_time <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">int64</span> end_time <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">option</span> <span style="color:#eceff4">(</span>buf.validate.message<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span>cel <span style="color:#81a1c1">=</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    id<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#34;event.start_time_before_end_time&#34;</span><span style="color:#eceff4">,</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    message<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#34;Start time must be before end time&#34;</span><span style="color:#eceff4">,</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    expression<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#34;this.start_time &lt; this.end_time&#34;</span><span style="color:#eceff4">,</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#eceff4">};</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>This example demonstrates a constraint involving two fields, <code>start_time</code> and <code>end_time</code>. A reasonable constraint is that start time must go before end time, so that&rsquo;s what this custom constraint is doing. The expression field contains the CEL expression that enforces this rule. CEL&rsquo;s simplicity makes it easy to define custom constraints. <code>message</code> allows you to customize the message.</p>
<p>And now are you ready for the big reveal? Every single standard constraint in protovalidate works the same way as this. It&rsquo;s all powered by CEL expressions. This not only provides you with a good reference to make your own custom constraints but it also makes it much easier to add language support for protovalidate. All you need is a good protobuf and CEL library, and you have most of the work already done for you.</p>
<h3 id="complete-example">Complete example</h3>
<p><a href="https://github.com/sudorandom/kmcd.dev/tree/main/content/posts/2025/protovalidate/example.proto" rel="external">See the full prototype from this post here.</a></p>
<p>See a full example protobuf file here. Reference protovalidate&rsquo;s <a href="https://buf.build/bufbuild/protovalidate/docs/main:buf.validate" rel="external"><code>validate.proto</code> file</a> to see every option available.</p>
<p>Now, let&rsquo;s see Protovalidate in action. We&rsquo;ll use <code>buf curl</code>, a command-line tool for interacting with gRPC and Connect services, to send a request to our example service and observe how Protovalidate enforces the defined validation rules.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>buf curl <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --protocol<span style="color:#81a1c1">=</span>grpcweb <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --http2-prior-knowledge <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    -d <span style="color:#a3be8c">&#39;{&#34;profile&#34;: {&#34;user&#34;: {}}}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    http://localhost:6660/example.ExampleService/CreateProfile
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#a3be8c">&#34;code&#34;</span>: <span style="color:#a3be8c">&#34;invalid_argument&#34;</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#a3be8c">&#34;message&#34;</span>: <span style="color:#a3be8c">&#34;validation error:\n - profile.user.name: value is required [required]\n - profile.user.email: value is empty, which is not a valid email address [string.email_empty]\n - profile.user.city: value does not match regex pattern `^[a-zA-Z]+(?:[\\s-][a-zA-Z]+)*</span>$<span style="color:#a3be8c">` [string.pattern]\n - profile.user.account_id: value must be greater than 0 [int64.gt]\n - profile.user.tags: value must contain at least 1 item(s) [repeated.min_items]\n - profile.user.created_at: value is required [required]&#34;</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#a3be8c">&#34;details&#34;</span>: <span style="color:#81a1c1">[</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>         <span style="color:#a3be8c">&#34;type&#34;</span>: <span style="color:#a3be8c">&#34;buf.validate.Violations&#34;</span>,
</span></span><span style="display:flex;"><span>         <span style="color:#a3be8c">&#34;value&#34;</span>: <span style="color:#a3be8c">&#34;CjAKEXByb2ZpbGUudXNlci5uYW1lEghyZXF1aXJlZBoRdmFsdWUgaXMgcmVxdWlyZWQKXAoScHJvZmlsZS51c2VyLmVtYWlsEhJzdHJpbmcuZW1haWxfZW1wdHkaMnZhbHVlIGlzIGVtcHR5LCB3aGljaCBpcyBub3QgYSB2YWxpZCBlbWFpbCBhZGRyZXNzCmgKEXByb2ZpbGUudXNlci5jaXR5Eg5zdHJpbmcucGF0dGVybhpDdmFsdWUgZG9lcyBub3QgbWF0Y2ggcmVnZXggcGF0dGVybiBgXlthLXpBLVpdKyg/Oltccy1dW2EtekEtWl0rKSokYApBChdwcm9maWxlLnVzZXIuYWNjb3VudF9pZBIIaW50NjQuZ3QaHHZhbHVlIG11c3QgYmUgZ3JlYXRlciB0aGFuIDAKTgoRcHJvZmlsZS51c2VyLnRhZ3MSEnJlcGVhdGVkLm1pbl9pdGVtcxoldmFsdWUgbXVzdCBjb250YWluIGF0IGxlYXN0IDEgaXRlbShzKQo2Chdwcm9maWxlLnVzZXIuY3JlYXRlZF9hdBIIcmVxdWlyZWQaEXZhbHVlIGlzIHJlcXVpcmVk&#34;</span>,
</span></span><span style="display:flex;"><span>         <span style="color:#a3be8c">&#34;debug&#34;</span>: <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a3be8c">&#34;violations&#34;</span>: <span style="color:#81a1c1">[</span>
</span></span><span style="display:flex;"><span>               <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;fieldPath&#34;</span>: <span style="color:#a3be8c">&#34;profile.user.name&#34;</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;constraintId&#34;</span>: <span style="color:#a3be8c">&#34;required&#34;</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;message&#34;</span>: <span style="color:#a3be8c">&#34;value is required&#34;</span>
</span></span><span style="display:flex;"><span>               <span style="color:#81a1c1">}</span>,
</span></span><span style="display:flex;"><span>               <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;fieldPath&#34;</span>: <span style="color:#a3be8c">&#34;profile.user.email&#34;</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;constraintId&#34;</span>: <span style="color:#a3be8c">&#34;string.email_empty&#34;</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;message&#34;</span>: <span style="color:#a3be8c">&#34;value is empty, which is not a valid email address&#34;</span>
</span></span><span style="display:flex;"><span>               <span style="color:#81a1c1">}</span>,
</span></span><span style="display:flex;"><span>               <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;fieldPath&#34;</span>: <span style="color:#a3be8c">&#34;profile.user.city&#34;</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;constraintId&#34;</span>: <span style="color:#a3be8c">&#34;string.pattern&#34;</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;message&#34;</span>: <span style="color:#a3be8c">&#34;value does not match regex pattern `^[a-zA-Z]+(?:[\\s-][a-zA-Z]+)*</span>$<span style="color:#a3be8c">`&#34;</span>
</span></span><span style="display:flex;"><span>               <span style="color:#81a1c1">}</span>,
</span></span><span style="display:flex;"><span>               <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;fieldPath&#34;</span>: <span style="color:#a3be8c">&#34;profile.user.account_id&#34;</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;constraintId&#34;</span>: <span style="color:#a3be8c">&#34;int64.gt&#34;</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;message&#34;</span>: <span style="color:#a3be8c">&#34;value must be greater than 0&#34;</span>
</span></span><span style="display:flex;"><span>               <span style="color:#81a1c1">}</span>,
</span></span><span style="display:flex;"><span>               <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;fieldPath&#34;</span>: <span style="color:#a3be8c">&#34;profile.user.tags&#34;</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;constraintId&#34;</span>: <span style="color:#a3be8c">&#34;repeated.min_items&#34;</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;message&#34;</span>: <span style="color:#a3be8c">&#34;value must contain at least 1 item(s)&#34;</span>
</span></span><span style="display:flex;"><span>               <span style="color:#81a1c1">}</span>,
</span></span><span style="display:flex;"><span>               <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;fieldPath&#34;</span>: <span style="color:#a3be8c">&#34;profile.user.created_at&#34;</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;constraintId&#34;</span>: <span style="color:#a3be8c">&#34;required&#34;</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;message&#34;</span>: <span style="color:#a3be8c">&#34;value is required&#34;</span>
</span></span><span style="display:flex;"><span>               <span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>         <span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>   <span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>Here&rsquo;s the response from the server. Notice the code: &ldquo;invalid_argument&rdquo; and the message field, which clearly explains the validation errors. Protovalidate caught six distinct issues: the <code>name</code> field was missing, the <code>email</code> was empty (and therefore invalid), the <code>city</code> didn&rsquo;t match the required regex, the <code>account_id</code> was zero (not greater than zero), the <code>tags</code> list was empty (requiring at least one item), and the <code>created_at</code> field was missing.</p>
<p>Now, let&rsquo;s correct the errors by providing valid data. We&rsquo;ll include a name, a valid email, and so on. When we fix all of these issues, suddenly it works!</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ buf curl <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --protocol<span style="color:#81a1c1">=</span>grpcweb <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --http2-prior-knowledge <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    -d <span style="color:#a3be8c">&#39;{
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">        &#34;profile&#34;: {
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">            &#34;address&#34;: {
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">                &#34;city&#34;: &#34;New York&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">                &#34;postal_code&#34;: &#34;10001&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">                &#34;street&#34;: &#34;25th Street West&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">            },
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">            &#34;user&#34;: {
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">                &#34;name&#34;: &#34;Bob&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">                &#34;email&#34;: &#34;bob@example.com&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">                &#34;account_id&#34;: 10,
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">                &#34;tags&#34;: [&#34;new_user&#34;],
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">                &#34;created_at&#34;: &#34;2023-01-01T00:00:00Z&#34;}}}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    http://localhost:6660/example.ExampleService/CreateProfile
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{}</span>
</span></span></code></pre></div><p>As you can see, this validation ensures the data conforms to our defined rules.</p>
<h2 id="my-side-projects-that-leverage-protovalidate">My side projects that leverage protovalidate</h2>
<p>You&rsquo;ve probably realized by now that I&rsquo;m a big fan of protovalidate. So it&rsquo;s no surprise for you to know that I&rsquo;ve used it in both of my larger personal projects recently:</p>
<h3 id="protoc-gen-connect-openapi">protoc-gen-connect-openapi</h3>
<p><a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external"><code>protoc-gen-connect-openapi</code></a> generates OpenAPI specifications for ConnectRPC servers. This uses protovalidate rules to further annotate the OpenAPI spec.</p>
<h3 id="fauxrpc">FauxRPC</h3>
<p><a href="https://fauxrpc.com/" rel="external"><code>FauxRPC</code></a> is my project that is a self-mocking protobuf driven server that supports gRPC/gRPC-Web/ConnectRPC/REST. Not only will it enforce protovalidate annotations on requests made to it but it also uses these annotations to generate more realistic mock data automatically. I feel like this really shows off the power of contract-driven APIs. I actually generated the examples above using FauxRPC!</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ buf build ssh://git@github.com/sudorandom/kmcd.dev.git#branch<span style="color:#81a1c1">=</span>main,subdir<span style="color:#81a1c1">=</span>content/posts/2025/protovalidate -o protovalidate-example.binpb
</span></span><span style="display:flex;"><span>$ fauxrpc run --schema protovalidate-example.binpb
</span></span></code></pre></div><h2 id="run-in-the-web">Run in the web</h2>
<p>Currently, protovalidate doesn&rsquo;t run on a web frontend, at least <del><a href="https://github.com/bufbuild/protovalidate/issues/67" rel="external">not yet</a></del> Update: <a href="https://github.com/bufbuild/protovalidate/issues/67#issuecomment-2846982958" rel="external">Now you can!</a>. I feel like this is the last piece that can finally unite input validation in the frontend and backend. I mentioned at the start of this article about how the frontend developers have to duplicate many of the rules that already exist in the backend. However, once a typescript version of protovalidate exists suddenly all of this work is completely handled simply by adding some protobuf annotations.</p>
<p>The potential of this library is incredible. I feel like we can have a world where types are defined in one place and used in any number of languages.</p>
<h2 id="rough-edges">Rough Edges</h2>
<p>Protovalidate does have a few rough edges. Overusing validation rules can sometimes make it hard to re-use types for multiple purposes. If you have a type that you use for a user and define a bunch of rules about required fields for that user type, how would you then update only a few of the fields for a user in a different call? I think there are ways around it, but <code>required</code> fields may in fact be the issue yet again. There&rsquo;s a past debate within Google about required fields and the result was the removal of the feature in core protobuf from proto2 to proto3. I feel like this may be the same issues manifesting again.</p>
<p>The recommended way of dealing with this situation is to avoid re-using types. Buf, the creators of protovalidate heavily dogfood their own validation library in their own APIs. You can see that they separate types meant <a href="https://buf.build/bufbuild/registry/docs/main:buf.registry.module.v1#buf.registry.module.v1.CreateModulesRequest" rel="external">for creating</a> and types meant <a href="https://buf.build/bufbuild/registry/docs/main:buf.registry.module.v1#buf.registry.module.v1.UpdateModulesRequest" rel="external">for updating</a>. This isn&rsquo;t super ideal because you likely want to keep most of the rules synchronized between both of these types but based on hard-earned experience, you often do have less problems if you simply create types for each use-case.</p>
<p>So instead of re-using a <code>User</code> type for &lsquo;create&rsquo; and &lsquo;update&rsquo; operations you could have another type for updating specific fields, like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">User</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1">string</span> email <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#616e87;font-style:italic">//... other fields
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">UserProfileUpdate</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">optional</span> <span style="color:#81a1c1">string</span> email <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">optional</span> <span style="color:#81a1c1">string</span> about_me <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#616e87;font-style:italic">//... other updatable fields
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><h2 id="what-to-take-away">What to take away</h2>
<p>Protovalidate is a powerful and versatile tool that simplifies input validation for Protobuf-based applications. By defining validation rules directly in your <code>.proto</code> files, you can ensure data integrity, reduce boilerplate code, and streamline your development process. With its support for custom constraints using CEL expressions, Protovalidate offers flexibility and extensibility for various use cases. While it currently lacks direct integration for web frontends, the potential for a unified validation approach across frontend and backend is super exciting.</p>
<p>So, why wait? Dive into protovalidate today and discover how it can revolutionize your protobuf development workflow. If you&rsquo;re already leveraging ConnectRPC or gRPC in one of the supported languages, this library is a no-brainer to try.</p>
]]></content:encoded></item><item><title>Behold! The Barcode Scanner</title><link>https://kmcd.dev/posts/barcode-scanner/</link><pubDate>Mon, 27 Jan 2025 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/barcode-scanner/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/barcode-scanner/cover_hu_a0105d126c57d428.webp" /> &lt;/p>
                
                
                </description><content:encoded><![CDATA[<p>I have a confession. I&rsquo;ve become <em>that</em> person. I recently got my hands on the <strong><a href="https://tera-digital.com/products/5100-laser-1d-wireless-barcode-scanner-wholesale" rel="external">5100 Laser 1D Wireless Barcode Scanner</a></strong>, and I&rsquo;m officially obsessed. My wife and I have started increasing our book collection, so keeping track of books that we have is starting to get a bit tedious. Spreadsheets, handwritten lists, trying to type in those ridiculously long <a href="https://en.wikipedia.org/wiki/ISBN" rel="external">ISBNs</a>&hellip; it was annoying.</p>
<p>Enter the barcode scanner.</p>
<figure><a href="https://kmcd.dev/posts/barcode-scanner/barcodescanner.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/barcode-scanner/barcodescanner_hu_670448827f298624.png"
         alt="My barcode scanner"/>
    </a>
</figure>

<p>You may have a pre-conceived notion that you need tons of super custom software to operate a barcode scanner but this barcode scanner <strong>acts just like a keyboard</strong>. You plug in the little USB receiver, scan a barcode, and <em>BAM</em>, the numbers appear whenever your text cursor is. Absolutely no required vendor software or drivers needed.</p>
<h2 id="why-i-love-it">Why I love it</h2>
<ul>
<li><strong>Fast:</strong> No more painstakingly typing out ISBNs.</li>
<li><strong>Accurate:</strong> Typos are a thing of the past. The scanner captures the information perfectly every time as long as you hear it beep.</li>
<li><strong>Spreadsheets:</strong> This thing makes updating a spreadsheet of ISBNs an absolute breeze.</li>
<li><strong>Organization:</strong> I can finally keep track of which books I&rsquo;ve loaned out, which ones I want to buy, and which ones are hiding in that stack in the corner.</li>
</ul>
<p>Personally, I&rsquo;ve been using <a href="https://librarything.com" rel="external">librarything.com</a> to track my budding book collection, but there are a few other alternatives like <a href="https://www.libib.com/" rel="external">libib.com</a>, <a href="https://clz.com/" rel="external">CLZ</a> and <a href="https://www.goodreads.com/" rel="external">GoodReads</a>. And I&rsquo;m considering using this for more use-cases, like board games, electronics, etc.</p>
<p>What do you think? Was this a waste of money or do you think it&rsquo;s a good idea?</p>
]]></content:encoded></item><item><title>Mixing CEL and Protobuf for Fun</title><link>https://kmcd.dev/posts/mixing-cel-and-protobuf-for-fun/</link><pubDate>Tue, 17 Dec 2024 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/mixing-cel-and-protobuf-for-fun/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/mixing-cel-and-protobuf-for-fun/cover_hu_9ce5b419b9b65439.webp" /> &lt;/p>
                
                
                </description><content:encoded><![CDATA[<p>Protobufs offer a structured approach to data definition, but testing gRPC services built with them can be a hurdle. By leveraging FauxRPC and CEL, you can accelerate development cycles, enhance test coverage, and ensure the reliability of your microservices.</p>
<h2 id="scaling-protobuf-testing">Scaling Protobuf Testing</h2>
<p><a href="https://grpc.io/" rel="external">gRPC</a> has emerged as a powerful framework for building efficient and scalable microservices, enabling seamless communication across diverse technologies. Its language-agnostic nature, powered by Protobuf, allows services written in Go, Java, Python, or any other supported language to interact effortlessly.</p>
<p>However, while gRPC excels at interoperability, generating test data that conforms to Protobuf specifications can still be a hurdle and most tools focus on testing specific languages. Imagine this: you&rsquo;re building a gRPC service in Go, but your clients might be written in Python, Java, or even C++. Ensuring your tests cover a wide range of scenarios with valid Protobuf messages for all these languages can become quite cumbersome. Now consider that each language has its own tooling for generating and using stub data. While this probably works for most language, I feel like this fragmentation of the ecosystem goes against the mission of protobuf and gRPC. Instead, I think a better approach is a language agnostic approach.</p>
<p>This is where <a href="https://fauxrpc.com/" rel="external"><strong>FauxRPC</strong></a> steps in, offering a streamlined approach to gRPC testing. Instead of crafting individual Protobuf responses for each test case, FauxRPC empowers you to define gRPC services that automatically generate realistic fake data for every response. This means you can effortlessly create a wide array of scenarios, simulate various data conditions, and thoroughly exercise your gRPC clients without the overhead of manually constructing complex Protobuf messages. However, this does fall short when you want to test specific test scenarios. That&rsquo;s where <a href="https://fauxrpc.com/docs/server/stubs/" rel="external">FauxRPC stubs</a> come in.</p>
<h2 id="the-pet-store-example">The Pet Store Example</h2>
<p>For the example, we are using <a href="https://github.com/connectrpc/vanguard-go/blob/main/internal/examples/pets/internal/proto/io/swagger/petstore/v2/pets.proto" rel="external">an example protobuf file from vanguard-go</a> for this demonstration.</p>
<h3 id="creating-a-basic-stub">Creating a basic stub</h3>
<p>First let&rsquo;s create a stub file at <code>get-pets.yaml</code> to stub the response to the <code>GetPetByID</code> method.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#8fbcbb">---</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">stubs</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>- <span style="color:#81a1c1">id</span><span style="color:#eceff4">:</span> get-pets-by-id-id-1
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">target</span><span style="color:#eceff4">:</span> io.swagger.petstore.v2.PetService/GetPetByID
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">content</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">id</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">category</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">id</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">name</span><span style="color:#eceff4">:</span> cat
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">name</span><span style="color:#eceff4">:</span> Whiskers
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">photo_urls</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>    - https://cataas.com/cat
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">tags</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#81a1c1">id</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">name</span><span style="color:#eceff4">:</span> cute
</span></span><span style="display:flex;"><span>    - <span style="color:#81a1c1">id</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">2</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">name</span><span style="color:#eceff4">:</span> kid-friendly
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">status</span><span style="color:#eceff4">:</span> available
</span></span></code></pre></div><p>This is a simple static stub. And will be returned any time <code>GetPetByID</code> is called.</p>
<p>Now start the FauxRPC server:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf build ssh://git@github.com/connectrpc/vanguard-go.git -o petstore.binpb --path internal/examples/pets/internal/proto/io/swagger/petstore/v2
</span></span><span style="display:flex;"><span>$ fauxrpc run --schema<span style="color:#81a1c1">=</span>petstore.binpb --only-stubs --stubs<span style="color:#81a1c1">=</span>get-pets.yaml
</span></span></code></pre></div><p>Notice that I actually ran two commands: the first one uses <code>buf build</code> to build a <a href="https://buf.build/docs/reference/images/" rel="external">buf image</a> using protobuf schema defined in the vanguard-go repo. The other actually starts the server. Let&rsquo;s break down the options used real quick:</p>
<ul>
<li><strong><code>--schema=petstore.binpb</code></strong>: tells FauxRPC about the schema. We use the buf image from the previous command for this.</li>
<li><strong><code>--only-stubs</code></strong>: tells FauxRPC to only use pre-defined stubs and to avoid generating random data when there are no relevant stubs defined.</li>
<li><strong><code>--stubs=get-pets.yaml</code></strong>: tells FauxRPC to load stubs from the get-pets.yaml file from above. This option can be specified multiple times and if you give it a directory it will recursively look for stub files to use.</li>
</ul>
<p>Now, when I hit the <code>GetPetByID</code> method, I now get Whiskers back.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl --http2-prior-knowledge -d <span style="color:#a3be8c">&#39;{&#34;pet_id&#34;: &#34;1&#34;}&#39;</span> http://127.0.0.1:6660/io.swagger.petstore.v2.PetService/GetPetByID
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;id&#34;</span>: <span style="color:#a3be8c">&#34;1&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;category&#34;</span>: <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;id&#34;</span>: <span style="color:#a3be8c">&#34;1&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;name&#34;</span>: <span style="color:#a3be8c">&#34;cat&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">}</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;name&#34;</span>: <span style="color:#a3be8c">&#34;Whiskers&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;photoUrls&#34;</span>: <span style="color:#81a1c1">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;https://cataas.com/cat&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">]</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;tags&#34;</span>: <span style="color:#81a1c1">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;id&#34;</span>: <span style="color:#a3be8c">&#34;1&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;name&#34;</span>: <span style="color:#a3be8c">&#34;cute&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">}</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;id&#34;</span>: <span style="color:#a3be8c">&#34;2&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;name&#34;</span>: <span style="color:#a3be8c">&#34;kid-friendly&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">]</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;status&#34;</span>: <span style="color:#a3be8c">&#34;available&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>If I were to add more stub entries, the response will be random amongst the applicable set of stubs. This is fine for some situations, but this also falls flat when you want to set up more complex test scenarios.</p>
<h3 id="lets-improve-this">Let&rsquo;s Improve This</h3>
<p>Enter <a href="https://cel.dev/" rel="external"><strong>CEL</strong> (Common Expression Language)</a>, a powerful and versatile expression language. With the latest release, FauxRPC now supports using CEL to define Protobuf messages. This unlocks a whole new level of conciseness, flexibility, and readability for your gRPC tests. Imagine creating dynamic messages, generating test data on the fly, and expressing complex scenarios with ease – all within your familiar Go environment.</p>
<p>So what does this mean? Well, there&rsquo;s three new attributes for FauxRPC stubs: <code>active_if</code> <code>priority</code> and <code>cel_content</code>. Let&rsquo;s improve upon the stubs that we defined above:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#8fbcbb">---</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">stubs</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>- <span style="color:#81a1c1">id</span><span style="color:#eceff4">:</span> get-pets-by-id-id-1
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">target</span><span style="color:#eceff4">:</span> io.swagger.petstore.v2.PetService/GetPetByID
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">active_if</span><span style="color:#eceff4">:</span> req.pet_id == 1
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">priority</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">100</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">content</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">id</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">category</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">id</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">name</span><span style="color:#eceff4">:</span> cat
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">name</span><span style="color:#eceff4">:</span> Whiskers
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">photo_urls</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>    - https://cataas.com/cat
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">tags</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#81a1c1">id</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">name</span><span style="color:#eceff4">:</span> cute
</span></span><span style="display:flex;"><span>    - <span style="color:#81a1c1">id</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">2</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">name</span><span style="color:#eceff4">:</span> kid-friendly
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">status</span><span style="color:#eceff4">:</span> available
</span></span><span style="display:flex;"><span>- <span style="color:#81a1c1">id</span><span style="color:#eceff4">:</span> get-pets-by-id-default
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">target</span><span style="color:#eceff4">:</span> io.swagger.petstore.v2.PetService/GetPetByID
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">cel_content</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">|</span><span style="color:#616e87">
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">    {
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;id&#39;: req.pet_id,
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;category&#39;: {&#39;id&#39;: gen, &#39;name&#39;: &#39;gen&#39;},
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;name&#39;: gen,
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;photo_urls&#39;: [gen, gen],
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;tags&#39;: [{&#39;id&#39;: gen, &#39;name&#39;: gen}],
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;status&#39;: gen
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">    }</span>
</span></span></code></pre></div><p>Well, the first entry now has two new lines: <code>active_if: req.pet_id == 1</code> and <code>priority: 100</code>. The <code>active_if</code> property lets us define an expression to decide if this stub should be used or not. In this case, if the request has <code>pet_id == 1</code> then we will use this stub. The second option sets this stub to the highest priority so it will be considered first.</p>
<p>Next, we added a new stub that uses <code>cel_contents</code>. This new field defines a CEL expression to generate a response for us. In this case we use <code>req.pet_id</code> from the request. This demonstrates You can reference the request message in order to craft more believable response messages. For every other field we generate random responses using the special <code>gen</code> value. Using <code>gen</code> will use the field descriptor to decide what kinds of data should be generated.</p>
<p>Here&rsquo;s what it looks like to call this metheod with a <code>pet_id</code> set to something other than 2:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl --http2-prior-knowledge -d <span style="color:#a3be8c">&#39;{&#34;pet_id&#34;: &#34;2&#34;}&#39;</span> http://127.0.0.1:6660/io.swagger.petstore.v2.PetService/GetPetByID
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;id&#34;</span>: <span style="color:#a3be8c">&#34;2&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;category&#34;</span>: <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;id&#34;</span>: <span style="color:#a3be8c">&#34;6775158014153383345&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;name&#34;</span>: gen
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">}</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;name&#34;</span>: <span style="color:#a3be8c">&#34;Mohammad&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;photoUrls&#34;</span>: <span style="color:#81a1c1">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;https://picsum.photos/400&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;https://picsum.photos/400&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">]</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;tags&#34;</span>: <span style="color:#81a1c1">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;id&#34;</span>: <span style="color:#a3be8c">&#34;3433292194552134769&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;name&#34;</span>: <span style="color:#a3be8c">&#34;Chad&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">]</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;status&#34;</span>: <span style="color:#a3be8c">&#34;deleted&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>As you can see, this makes stubs much more dynamic. The output can be based on the input so at the very least the <code>id</code> field can match. Let&rsquo;s delve deeper into how CEL can be used to create more sophisticated and dynamic test scenarios.</p>
<h4 id="conditional-logic">Conditional Logic</h4>
<p>Imagine a scenario where you want to return different responses based on specific conditions in the request. You could use CEL to define these conditions:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#81a1c1">id</span><span style="color:#eceff4">:</span> get-pets-by-id-conditional
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">target</span><span style="color:#eceff4">:</span> io.swagger.petstore.v2.PetService/GetPetByID
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">cel_content</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">|</span><span style="color:#616e87">
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">    {
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;id&#39;: req.pet_id,
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;category&#39;: {&#39;id&#39;: 1, &#39;name&#39;: &#39;cat&#39;},
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;name&#39;: &#39;Whiskers&#39;,
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;photoUrls&#39;: [&#39;https://cataas.com/cat&#39;],
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;tags&#39;: [{&#39;id&#39;: 1, &#39;name&#39;: &#39;cute&#39;}, {&#39;id&#39;: 2, &#39;name&#39;: &#39;kid-friendly&#39;}],
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;status&#39;: &#39;available&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">    }å
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">    if req.pet_id == 1 else {
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;id&#39;: req.pet_id,
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;category&#39;: {&#39;id&#39;: 2, &#39;name&#39;: &#39;dog&#39;},
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;name&#39;: &#39;Buddy&#39;,
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;photoUrls&#39;: [&#39;https://dog.ceo/api/breeds/image/random&#39;],
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;tags&#39;: [{&#39;id&#39;: 3, &#39;name&#39;: &#39;friendly&#39;}, {&#39;id&#39;: 4, &#39;name&#39;: &#39;playful&#39;}],
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;status&#39;: &#39;pending&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">    }</span>
</span></span></code></pre></div><p>In this example, if the <code>pet_id</code> is 1, the first response is returned. Otherwise, the second response is returned.</p>
<h4 id="dynamic-data-generation">Dynamic Data Generation</h4>
<p>CEL can be used to generate dynamic data based on various factors, such as integers, usernames, sentences, random numbers, or values from the request:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#81a1c1">id</span><span style="color:#eceff4">:</span> get-pets-by-id-dynamic
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">target</span><span style="color:#eceff4">:</span> io.swagger.petstore.v2.PetService/GetPetByID
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">cel_content</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">|</span><span style="color:#616e87">
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">    {
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;id&#39;: req.pet_id,
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;category&#39;: [{&#39;id&#39;: 1, &#39;name&#39;: &#39;cat&#39;}, {&#39;id&#39;: 2, &#39;name&#39;: &#39;dog&#39;}][fake_int() % 2],
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;name&#39;: [&#39;Mr&#39;, &#39;Ms&#39;][fake_int() % 2] + &#39; &#39; + fake_first_name(),
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;photoUrls&#39;: [&#39;https://picsum.photos/200&#39;],
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;tags&#39;: [{&#39;id&#39;: gen, &#39;name&#39;: gen}],
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">        &#39;status&#39;: [&#39;available&#39;, &#39;pending&#39;, &#39;sold&#39;][fake_int() % 3]
</span></span></span><span style="display:flex;"><span><span style="color:#616e87">    }</span>
</span></span></code></pre></div><ul>
<li><strong><code>id</code></strong>: This represents the unique identifier of a pet. It takes its value directly from <code>req.pet_id</code>, which is pulled the pet ID from the incoming request.</li>
<li><strong><code>category</code></strong>: This defines the pet&rsquo;s category. <code>fake_int() % 2</code> is used to randomly select one of the categories since the result of the modulo operation will be either 0 or 1.</li>
<li><strong><code>name</code></strong>: This generates a random pet name. It combines &lsquo;Mr&rsquo; or &lsquo;Ms&rsquo; (chosen randomly using <code>fake_int() % 2</code>) with a fake first name generated by the <code>fake_first_name()</code> function.</li>
<li><strong><code>photoUrls</code></strong>: This field is meant to hold an array of URLs pointing to pet photos. In this case, it&rsquo;s a single-element array with a placeholder URL from <code>https://picsum.photos/200</code> (which generates a random image).</li>
<li><strong><code>tags</code></strong>: This defines an array of tags associated with the pet. <code>gen</code> is used to generate a random (but type appropriate) value for a single tag&rsquo;s ID and name fields.</li>
<li><strong><code>status</code></strong>: This indicates the pet&rsquo;s current status. It randomly selects one of three possible values (&lsquo;available&rsquo;, &lsquo;pending&rsquo;, or &lsquo;sold&rsquo;) using <code>fake_int() % 3</code>.</li>
</ul>
<p>All of the functions starting with <code>fake_</code> are provided by a new FauxRPC package, <a href="https://github.com/sudorandom/fauxrpc/tree/main/celfakeit" rel="external">celfakeit</a>. This library exposes many functions from <a href="https://github.com/brianvoe/gofakeit" rel="external">gofakeit</a> as CEL functions.</p>
<h2 id="recap">Recap</h2>
<p>Since I&rsquo;ve been making a lot of changes and improvements, it seems like a good idea to recap how FauxRPC works and how it&rsquo;s different from other similar tools.</p>
<ul>
<li>FauxRPC does NOT require code generation. It takes in <a href="https://protobuf.dev/" rel="external">protobuf files</a>, <a href="https://buf.build/docs/reference/descriptors/" rel="external">descriptors</a>, or <a href="https://buf.build/docs/reference/images/" rel="external">buf images</a>.</li>
<li>FauxRPC is meant to be language agnostic. This was important to me since gRPC is often used across many languages so tying it to just Go, for example, adds un-needed ecosystem fragmentation.</li>
<li>FauxRPC has <a href="https://kmcd.dev/posts/fauxrpc-protovalidate/" rel="external">protovalidate support</a></li>
<li>FauxRPC <a href="https://kmcd.dev/posts/fauxrpc-testcontainers/" rel="external">works with testcontainers</a>. There is a <a href="https://pkg.go.dev/github.com/sudorandom/fauxrpc/testcontainers" rel="external">built in library</a> for Go (see <a href="https://github.com/sudorandom/fauxrpc/blob/main/testcontainers/testcontainers_test.go" rel="external">the testcontainer tests</a> for examples of how this can be used). It shouldn&rsquo;t be too hard to create similar libraries in other languages.</li>
<li>You can <a href="https://fauxrpc.com/docs/server/stubs/" rel="external">now return pre-defined stubs</a> which can include errors, static responses, or dynamic (CEL-based) content.</li>
</ul>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">

    
    
        
        
        <img src="https://kmcd.dev/posts/mixing-cel-and-protobuf-for-fun/diagram.svg" class="center" width="800px"/>
    



    </span>

    
</div>

<h2 id="conclusion">Conclusion</h2>
<p>In conclusion, the combination of Protobuf and CEL offers a powerful and flexible approach to testing gRPC services. By leveraging CEL&rsquo;s expressive power, developers can define dynamic and realistic test data, significantly enhancing the efficiency and effectiveness of their testing efforts.</p>
<p>FauxRPC, with the integration of CEL, simplifies the process of generating complex Protobuf messages, allowing developers to focus on writing robust and reliable gRPC services. By embracing this innovative approach, teams can accelerate their development cycles and deliver high-quality gRPC applications with confidence.</p>
<p><em>&hellip; and don&rsquo;t forget to <a href="https://github.com/sudorandom/fauxrpc" rel="external">star the repo on GitHub</a>. It helps more than you know!</em></p>
]]></content:encoded></item><item><title>FauxRPC and Protovalidate</title><link>https://kmcd.dev/posts/fauxrpc-protovalidate/</link><pubDate>Tue, 12 Nov 2024 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/fauxrpc-protovalidate/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/fauxrpc-protovalidate/cover_hu_cc063547c579daa5.webp" /> &lt;/p>
                
                
                </description><content:encoded><![CDATA[<p><a href="https://fauxrpc.com/" rel="external">FauxRPC</a>, a tool for generating fake gRPC servers, now integrates with <a href="https://github.com/bufbuild/protovalidate" rel="external">protovalidate</a>, which lets you define validation rules in your Protobuf definitions. Now every request processed by FauxRPC will be automatically validated against your protovalidate rules. Not only will you get high quality data validation in your application, but now you can have the same validation before you even write your application logic! Let&rsquo;s walk through how this new feature works.</p>
<h2 id="how-it-works">How it works</h2>
<p>First, define your validation rules using protovalidate&rsquo;s constraint annotations within your Protobuf definitions. For guidance, you should reference <a href="https://github.com/bufbuild/protovalidate/blob/main/docs/README.md" rel="external">the protovalidate documentation</a>. Fauxrpc will take care of the rest, automatically validating each request against these rules. If a request fails validation, a detailed error message will be returned, guiding you toward quickly fixing the issue.</p>
<p>For this example, we&rsquo;re going to use a simple service where you can call with a name and it will return a greeting. Here&rsquo;s what that would look like in protobuf:</p>
<p><strong>greet.proto</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">greet</span><span style="color:#81a1c1">.</span>v1<span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#a3be8c">&#34;buf/validate/validate.proto&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GreetRequest</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span> <span style="color:#eceff4">[(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1">string</span> <span style="color:#81a1c1">=</span> <span style="color:#eceff4">{</span>min_len<span style="color:#81a1c1">:</span> <span style="color:#b48ead">3</span><span style="color:#eceff4">,</span> max_len<span style="color:#81a1c1">:</span> <span style="color:#b48ead">20</span><span style="color:#eceff4">}];</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GreetResponse</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> greeting <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span> <span style="color:#eceff4">[(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1">string</span><span style="color:#81a1c1">.</span>example <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;Hello, user!&#34;</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">service</span> GreetService <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">rpc</span> Greet<span style="color:#eceff4">(</span>GreetRequest<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span> <span style="color:#eceff4">(</span>GreetResponse<span style="color:#eceff4">)</span> <span style="color:#eceff4">{}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>The <code>buf.validate.field</code> annotations are from protovalidate. The section <code>(buf.validate.field).string = {min_len: 3, max_len: 20}</code> ensures that the <code>name</code> field is between 3 and 20 characters. <code>(buf.validate.field).string.example = &quot;Hello, user!&quot;</code> is declaring what an example response might look like. This mostly serves as documentation but it also influences other tools, including <a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">protoc-gen-connect-openapi</a> and, now, FauxRPC! We&rsquo;ll see how FauxRPC uses both of these constraints in a second. First, let&rsquo;s start up the FauxRPC server.</p>
<p>To use protovalidate with FauxRPC, we need to leverage another tool that&rsquo;s extremely helpful for working with protobuf definitions. This is because we used protovalidate, a dependency. Usually dependencies are hard to deal with in protobuf because the default protobuf tooling just doesn&rsquo;t manage dependencies at all. However, the <a href="https://buf.build/product/cli" rel="external">Buf CLI</a> and the <a href="https://buf.build/product/bsr" rel="external">BSR</a> can help us here.</p>
<p>First, create a new <code>buf.yaml</code> file in the same directory as <code>greet.proto</code> with the contents:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#81a1c1">version</span><span style="color:#eceff4">:</span> v2
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">deps</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>  - buf.build/bufbuild/protovalidate
</span></span></code></pre></div><p>Now we&rsquo;re just a few commands away from having a mock service running:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Get Buf CLI to pull our new dependency</span>
</span></span><span style="display:flex;"><span>$ buf dep update
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Build our protobuf file (and dependencies) into a protobuf &#34;image&#34;: https://buf.build/docs/build/overview/</span>
</span></span><span style="display:flex;"><span>$ buf build . -o greet.binpb
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Start FauxRPC with this image</span>
</span></span><span style="display:flex;"><span>$ fauxrpc run --schema<span style="color:#81a1c1">=</span>greet.binpb
</span></span></code></pre></div><p>Now let&rsquo;s try some requests against this new service:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl --http2-prior-knowledge -d <span style="color:#a3be8c">&#39;{}&#39;</span> http://127.0.0.1:6660/greet.v1.GreetService/Greet
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#a3be8c">&#34;code&#34;</span>: <span style="color:#a3be8c">&#34;invalid_argument&#34;</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#a3be8c">&#34;message&#34;</span>: <span style="color:#a3be8c">&#34;validation error:\n - name: value length must be at least 3 characters [string.min_len]&#34;</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#a3be8c">&#34;details&#34;</span>: <span style="color:#81a1c1">[</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>         <span style="color:#a3be8c">&#34;type&#34;</span>: <span style="color:#a3be8c">&#34;buf.validate.Violations&#34;</span>,
</span></span><span style="display:flex;"><span>         <span style="color:#a3be8c">&#34;value&#34;</span>: <span style="color:#a3be8c">&#34;CkIKBG5hbWUSDnN0cmluZy5taW5fbGVuGip2YWx1ZSBsZW5ndGggbXVzdCBiZSBhdCBsZWFzdCAzIGNoYXJhY3RlcnM&#34;</span>,
</span></span><span style="display:flex;"><span>         <span style="color:#a3be8c">&#34;debug&#34;</span>: <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a3be8c">&#34;violations&#34;</span>: <span style="color:#81a1c1">[</span>
</span></span><span style="display:flex;"><span>               <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;fieldPath&#34;</span>: <span style="color:#a3be8c">&#34;name&#34;</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;constraintId&#34;</span>: <span style="color:#a3be8c">&#34;string.min_len&#34;</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a3be8c">&#34;message&#34;</span>: <span style="color:#a3be8c">&#34;value length must be at least 3 characters&#34;</span>
</span></span><span style="display:flex;"><span>               <span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>         <span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>   <span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>Oh, duh! We hit the length constraint we added for the <code>name</code> field, so our empty object <code>{}</code> isn&rsquo;t good enough anymore. We need a name and it needs to have between 3 and 20 characters. Let&rsquo;s try once more:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl --http2-prior-knowledge -d <span style="color:#a3be8c">&#39;{&#34;name&#34;: &#34;Bob&#34;}&#39;</span> http://127.0.0.1:6660/greet.v1.GreetService/Greet
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;greeting&#34;</span>: <span style="color:#a3be8c">&#34;Hello, user!&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>Great, we received a response! And the response is populated using our <code>(buf.validate.field).string.example</code> annotation. This will allow us to stand up a fake service with a few simple commands that has request validation built in and will use the constraints to make realistic fake data.</p>
<p>This shows how protovalidate uses protovalidate constraints both for input validation and fake data generation.</p>
<p>On a related note, I am personally excited to eventually have <a href="https://github.com/bufbuild/protovalidate/issues/67" rel="external">Typescript Support for protovalidate</a>. That would allow us to use protovalidate for both clean frontend UX and robust server side validation. In my eyes, this would completely solve the problem of having duplicate and inconsistent validation rules between the frontend and backend. Backend needs these rules to ensure system reliability and consistency. Frontend needs these rules for better UX.</p>
<h2 id="benefits-for-you">Benefits for you</h2>
<p>FauxRPC and protobuf synergizes well with the model-driven API design with protobuf. From a single API definition you have strongly typed definitions that has support for many programming languages, powerful validation constraints, examples of what each field looks like. And FauxRPC lets you experiment with all of that before writing a single line of application code. This means:</p>
<ul>
<li><strong>Reduced development time:</strong> Spend less time debugging data issues and more time building amazing features.</li>
<li><strong>Increased confidence:</strong> Trust that your RPCs are handling data correctly, leading to more stable and reliable applications.</li>
<li><strong>Improved collaboration:</strong> Make it easier for teams to work together by ensuring everyone adheres to the same data standards.</li>
</ul>
<h2 id="ready-to-give-it-a-try">Ready to give it a try?</h2>
<p>Ready to experience the power of FauxRPC and protovalidate?</p>
<ul>
<li>Update to the latest version of FauxRPC: <a href="https://github.com/sudorandom/fauxrpc/releases/latest" rel="external">github.com/sudorandom/fauxrpc/releases/latest</a></li>
<li>Learn more about protovalidate: <a href="https://github.com/bufbuild/protovalidate" rel="external">github.com/bufbuild/protovalidate</a></li>
<li>Explore the FauxRPC documentation: <a href="https://fauxrpc.com/" rel="external">fauxrpc.com</a></li>
</ul>
<p>For reference, all of the code in the article <a href="https://github.com/sudorandom/kmcd.dev/tree/main/content/posts/2024/fauxrpc-protovalidate/proto" rel="external">is available here</a>.</p>
]]></content:encoded></item><item><title>The Call of the Monolithic Codebase</title><link>https://kmcd.dev/posts/call-of-the-monolith-codebase/</link><pubDate>Tue, 22 Oct 2024 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/call-of-the-monolith-codebase/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/call-of-the-monolith-codebase/cover_hu_db19af5e196aee10.webp" /> &lt;/p>
                
                Are monoliths cool again?
                </description><content:encoded><![CDATA[<p>As the microservices landscape continues to evolve, a sobering reality is setting in for many organizations: managing many microservice repositories has become a significant hurdle. In the pursuit of scalability and flexibility offered by microservices, we may have inadvertently sacrificed maintainability, but now the pendulum is swinging back towards simpler code management while retaining the ability to deploy at scale. For organizations already entrenched in the microservices world, it&rsquo;s time to reconsider an old friend: the <strong>monolithic codebase</strong>.</p>
<p>Devs at these organizations might have noticed that as the number of services grow, so does the complexity of managing multiple codebases, each with its own repository, build pipelines, etc. This explosion of the number of repos introduces significant overhead, including:</p>
<ul>
<li><strong>Increased Operational Complexity:</strong> More repositories mean more CI/CD pipelines, dependency management, and security audits.</li>
<li><strong>Higher Cognitive Load for Developers:</strong> Context switching between numerous repositories can decrease productivity. &ldquo;Oh, this repo uses the old docker-compose scheme for testing, we should really remember update that.&rdquo;</li>
<li><strong>Unified Change Management Challenges:</strong> Implementing organization-wide changes, such as dependency updates or coding standards, becomes a monumental task.</li>
</ul>
<h3 id="scenario-updating-log4j-across-20-microservices-repositories">Scenario: Updating <code>log4j</code> across 20 Microservices Repositories</h3>
<p>To further illustrate the challenges of managing multiple repositories in a microservices architecture, let&rsquo;s delve into the process of updating a commonly used library across all microservice repos.</p>
<ul>
<li><strong>Discovery:</strong>
<ul>
<li>Identify the need to update <code>log4j</code> due to <a href="https://blog.cloudflare.com/inside-the-log4j2-vulnerability-cve-2021-44228/" rel="external">a security vulnerability</a>. Maybe you hear about this from dependabot spamming you, maybe another security scanner picked up that your repo is using a log4j, maybe your devs see it at the top of hacker news or maybe you just have a grumpy security operations team reviewing every CVE and deciding if anything the company uses is vulnerable. Hopfully it&rsquo;s all of the above if the vulnerability is really severe.</li>
<li>Determine the versions currently in use across all 20 microservices.</li>
</ul>
</li>
<li><strong>Planning:</strong>
<ul>
<li>Coordinate with multiple team leads to schedule the update. Depending on how &ldquo;mono&rdquo; the &ldquo;monolith&rdquo; is, you may have several teams sharing the same codebase.</li>
<li>Ensure compatibility with each microservice&rsquo;s specific dependencies.</li>
</ul>
</li>
<li><strong>Execution:</strong>
<ol>
<li><strong>Update Dependency:</strong>
<ul>
<li>Modify the <code>pom.xml</code> (Maven) or <code>build.gradle</code> (Gradle) in each of the 20 repositories.</li>
<li>Commit changes with a consistent, informative message (e.g., &ldquo;Security: Update log4j to v2.17.1&rdquo;).</li>
</ul>
</li>
<li><strong>Verify and Test:</strong>
<ul>
<li>Run automated tests for each microservice to catch any breaking changes.</li>
<li>Perform manual testing where automated coverage is insufficient.</li>
</ul>
</li>
<li><strong>Deployment:</strong>
<ul>
<li>Trigger CI/CD pipelines for each microservice to deploy the updated library.</li>
</ul>
</li>
</ol>
</li>
<li><strong>Verification and Monitoring:</strong>
<ul>
<li>Confirm the update has been successfully deployed across all microservices.</li>
<li>Monitor application logs for any issues related to the <code>log4j</code> update.</li>
</ul>
</li>
</ul>
<h4 id="challenges-encountered">Challenges Encountered</h4>
<p>Now this is the process if everything goes well. There&rsquo;s several different points where this process can increase the complexity of our simple task:</p>
<ul>
<li><strong>Version Inconsistencies:</strong> Different microservices were using various versions of <code>log4j</code>, requiring tailored updates. Maybe some repos were on a previous major version and you&rsquo;ll need to update a lot of code in order to upgrade to the latest version. Or you may consider backporting the fix and maintaining patches or a fork of this repository.</li>
<li><strong>Dependency Conflicts:</strong> Updates in some microservices revealed hidden dependencies on outdated libraries. Maybe the latest version of log4j pulls in a new version of apache commons. Maybe another dependency, for some reason, doesn&rsquo;t work with the latest version. This kind of thing does happen and can be a real pain to resolve.</li>
</ul>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/call-of-the-monolith-codebase/limes_hu_477e99f379d196ce.webp" class="center" width="500px"/>
    


<p>With all of this tedious work you should remember that this type of update is <em>the simplest kind of change to make</em>. Now just imagine trying to be proactive and keep all of your services up-to-date and to not let the least loved ones get left behind.</p>
<h4 id="monolithic-codebase-alternative">Monolithic Codebase Alternative</h4>
<p>Because of the overhead in this process, some developers have leaned towards consolidating most of the code that powers microservices into a single repo. We refer to this type of code repository as a &ldquo;monorepo&rdquo;. In our log4j scenario, it would help with several of these steps.</p>
<ul>
<li><strong>Single Update Point:</strong> Modify the <code>log4j</code> dependency in one place, within the unified monorepo.</li>
<li><strong>Simplified Testing and Deployment:</strong> Leverage the monorepo&rsquo;s integrated testing and deployment mechanisms. If this is a commonly used library, more automated tests will need to be ran, but these are tests that would have ran anyway, but just spread out amongst many different repositories.</li>
<li><strong>Reduced Complexity and Resource Usage:</strong> Minimize the logistical overhead and focus on verifying the update&rsquo;s success.</li>
</ul>
<h2 id="the-allure-of-the-monolithic-codebase">The Allure of the Monolithic Codebase</h2>
<p>As demonstrated by the <code>log4j</code> update scenario, managing numerous repositories can be a complex and time-consuming process. A monolithic codebase offers a compelling alternative by simplifying many aspects of development and maintenance. Let&rsquo;s explore some of its key advantages:</p>
<ul>
<li><strong>Simplified Repository Management:</strong> Instead of juggling 20 separate repositories, a monorepo consolidates everything into a single location. This significantly reduces the operational overhead associated with managing multiple codebases, build pipelines, and deployment processes. Imagine the time saved by not having to update <code>log4j</code> in 20 different places!</li>
<li><strong>Unified Codebase Governance:</strong> Implementing organization-wide changes, like security updates or coding standard enhancements, becomes significantly easier with a monorepo. A single update propagates across all services, ensuring consistency and reducing the risk of inconsistencies or oversights that can occur when managing multiple repositories.</li>
<li><strong>Improved Developer Experience:</strong> Developers can navigate the entire codebase more easily, reducing the cognitive load associated with context switching between different repositories. No more remembering which repo uses a specific Docker Compose scheme or wondering where a particular service is located. Everything is readily accessible in one place.</li>
<li><strong>Enhanced Code Reusability:</strong> With all services residing in a single repository, code sharing and reuse become more straightforward. Developers can easily identify and leverage existing components, reducing code duplication and promoting consistency across the application. This can be particularly beneficial for common libraries like <code>log4j</code>, where a single, well-maintained implementation can be used by all services.</li>
</ul>
<p>Furthermore, a monorepo can significantly streamline testing and deployment processes:</p>
<ul>
<li><strong>Simplified Testing:</strong> Automated tests can be run across all affected services with a single command, ensuring that changes don&rsquo;t introduce unexpected regressions. In the <code>log4j</code> scenario, this means verifying the update&rsquo;s impact on all services simultaneously, rather than testing each microservice individually.</li>
<li><strong>Streamlined Deployment:</strong> Atomic deployments become possible, guaranteeing that all services are updated consistently. This eliminates the risk of partial deployments or version mismatches that can occur in a microservices architecture.</li>
<li><strong>Easier Rollbacks:</strong> If an issue arises after deployment, rolling back to a previous version becomes simpler with a monorepo. A single rollback operation can revert all services to a known good state, minimizing downtime and disruption. With microservices that all have different versions, it&rsquo;s hard to know what a good combination of versions for each service actually is.</li>
</ul>
<p>Note that while the codebase may be monolithic, many developer teams are still deploying as microservices. This promises the best of both worlds: the simplified and streamlined experience of a single repo with the scalability properties of microservices.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/call-of-the-monolith-codebase/assimilate_hu_eb15f835a2c64be4.webp" class="center" width="500px"/>
    


<p>However, it&rsquo;s important to acknowledge that monolithic codebases are not without their challenges.</p>
<h2 id="tooling-for-a-successful-monolithic-codebase">Tooling for a Successful Monolithic Codebase</h2>
<p>To leverage the benefits of a monolithic codebase while avoiding the pitfalls of tight coupling and decreased maintainability, consider the following tools:</p>
<ul>
<li><strong><a href="https://bazel.build:/" rel="external">Bazel</a>:</strong> A build tool and ecosystem that supports large, multi-language monorepos; created by Google.</li>
<li><strong><a href="https://www.pantsbuild.org:/" rel="external">Pants</a>:</strong> A build system for monorepos, focusing on performance and scalability.</li>
<li><strong><a href="https://buck2.build:/" rel="external">Buck</a>:</strong> A fast and scalable build tool developed by Facebook, designed to handle large, complex monorepos with ease.</li>
<li><strong><a href="https://serviceweaver.dev/" rel="external">Service Weaver</a>:</strong> Service weaver has a very different take on monorepos. It&rsquo;s more than just a build tool, and is more of a framework that allows for monorepos with monolithic or microservice deployment. Because it is opinionated, other kinds of build tools aren&rsquo;t needed as much. Also, this is only supported for Go.</li>
<li><strong><a href="https://lerna.js.org/" rel="external">Lerna</a>:</strong> A popular and mature tool for managing JavaScript monorepos, especially useful for managing multiple packages within a single repository.</li>
<li><strong><a href="https://turbo.build/" rel="external">Turbo</a>:</strong> High-performance build system for JavaScript/TypeScript monorepos, known for its speed and efficient caching mechanisms.</li>
<li><strong><a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules" rel="external">Git Submodules</a>:</strong> While not a dedicated monorepo tool, Git submodules can be used to manage dependencies on other Git repositories within a larger monorepo. However, they can be complex to use and may not be the best choice for all scenarios.</li>
<li><strong>Your own tooling:</strong> Sometimes building a set of scripts to customize the build process using more traditional tooling may be better.</li>
</ul>
<p>You should note that this list is by no means complete. There are a lot of options (I think I found around 10 Javascript tools before giving up my research). I also left out several tools don&rsquo;t have open source tooling or rely exclusively on paid hosting services.</p>
<p>The choice of tooling will highly depend on the language/languages your codebases are hosted in. Bazel excels at seamlessly handling polyglot repos no matter the language. Others, like Pants only support a handful of languages and if you&rsquo;re using another language you may need to use multiple tools to get the job done. In general though, the fewer languages the easier it is to integrate because there are always language-specific idiosyncrasies that pop up, even when using tools like Bazel which try to hide those differences from you. Choose what makes sense for your team&hellip; and maybe try to get as far as you can using standard tooling before introducing extra build tools.</p>
<h2 id="drawbacks-of-monolithic-codebases">Drawbacks of Monolithic Codebases</h2>
<p>You should consider some of the real drawbacks when working with a monolithic codebase.</p>
<ul>
<li><strong>Increased Build Times:</strong> As the codebase grows, build times can become significantly longer, especially if not properly optimized. This can slow down development and deployment cycles.</li>
<li><strong>Merge Conflicts:</strong> With many developers working on the same codebase, merge conflicts can become more frequent and complex, requiring more time and effort to resolve.</li>
<li><strong>Tight Coupling:</strong> If not carefully managed, a monorepo can lead to tight coupling between different parts of the application, making it harder to modify or refactor individual components without affecting others.</li>
<li><strong>Learning Curve:</strong> For developers accustomed to working with microservices and separate repositories, there can be a learning curve associated with navigating and understanding a large, monolithic codebase.</li>
<li><strong>Access Control:</strong> Managing access control and permissions can be more challenging in a monorepo, as developers potentially have access to the entire codebase, even if they only work on a specific part of it.</li>
</ul>
<h3 id="bazel-specific-drawbacks">Bazel-Specific Drawbacks</h3>
<p>Since Bazel is a popular choice for managing monorepos, it&rsquo;s worth noting some of its specific drawbacks:</p>
<ul>
<li><strong>Steep Learning Curve:</strong> Bazel has a reputation for having a steep learning curve, especially for developers unfamiliar with build systems like <a href="https://makefiletutorial.com/" rel="external">Make</a> or <a href="https://cmake.org/" rel="external">CMake</a>. Its configuration language, Starlark, can also take time to master.</li>
<li><strong>Limited IDE Support:</strong> While IDE support for Bazel is improving, it&rsquo;s still not as comprehensive as for other build systems. This can make debugging and code navigation more challenging.</li>
<li><strong>Performance Issues:</strong> While Bazel is generally performant, it can sometimes experience performance issues, especially with very large codebases or complex build configurations.</li>
<li><strong>Community Support:</strong> While Bazel has a growing community, it&rsquo;s still smaller than the communities for other build systems like Maven, Gradle, or default tooling from languages like Rust, Go, etc. This can make it harder to find help or resources when encountering issues.</li>
</ul>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/call-of-the-monolith-codebase/bazel_hu_c96df32063fe8943.webp" class="center" width="500px"/>
    


<h2 id="mitigating-the-drawbacks">Mitigating the Drawbacks</h2>
<p>It&rsquo;s important to note that many of these drawbacks can be mitigated with careful planning and the right tools and practices:</p>
<ul>
<li><strong>Modular Design:</strong> Employing modular design principles and clear code organization can help minimize tight coupling and improve build times.</li>
<li><strong>Code Ownership:</strong> Establish clear code ownership and access control policies to manage permissions and prevent unauthorized modifications.</li>
<li><strong>Continuous Integration and Continuous Deployment (CI/CD):</strong> Implement robust CI/CD pipelines to automate builds, tests, and deployments, helping to identify and address issues early on.</li>
<li><strong>Invest in Training:</strong> Provide adequate training and support to help developers learn Bazel and its best practices.</li>
<li><strong>Monitoring and Optimization:</strong> Regularly monitor build times and Bazel&rsquo;s performance to identify and address any bottlenecks. Many of these build systems have more advanced features like remote caching and remote execution, which can be used to greatly reduce build times.</li>
</ul>
<p>By acknowledging these potential drawbacks and taking steps to mitigate them, you can increase the likelihood of successfully adopting a monolithic codebase with Bazel.</p>
<h2 id="the-path-forward">The Path Forward</h2>
<p>While a monolithic codebase can simplify management and development, it&rsquo;s crucial to strike a balance between unity and the autonomy that microservices provide. Consider the following:</p>
<ul>
<li><strong>Start Small:</strong> If migrating from a microservices architecture, begin by consolidating closely related services.</li>
<li><strong>Establish Clear Boundaries:</strong> Use tooling and guidelines to maintain logical separations within the monorepo.</li>
<li><strong>Monitor and Adapt:</strong> Continuously evaluate the effectiveness of your monolithic approach and make adjustments as needed.</li>
</ul>
<p>As we&rsquo;ve explored the trade-offs between separate repos and monolithic codebases, it&rsquo;s clear that the latter can offer a more streamlined development experience, simplified management, and reduced overhead. However, before embracing a monolithic codebase, it&rsquo;s essential to carefully consider the implications of this approach on your project&rsquo;s specific needs.</p>
]]></content:encoded></item><item><title>FauxRPC + Test Containers</title><link>https://kmcd.dev/posts/fauxrpc-testcontainers/</link><pubDate>Tue, 15 Oct 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/fauxrpc-testcontainers/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/fauxrpc-testcontainers/cover_hu_34ecac18b5af762a.webp" /> &lt;/p>
                
                Effortless gRPC Mocking in Go
                </description><content:encoded><![CDATA[<p>Testing gRPC services can be tricky. You often need a real server running, which can introduce complexity and slow down your tests. Enter <strong><a href="https://fauxrpc.com" rel="external">FauxRPC</a></strong> + <strong><a href="https://testcontainers.com/" rel="external">Testcontainers</a></strong>, and small <a href="https://github.com/sudorandom/fauxrpc/blob/main/testcontainers/testcontainers.go" rel="external">Go package</a> that simplifies gRPC mocking.</p>
<p>To address challenges with testing while using gRPC services, we can leverage the power of <a href="https://testcontainers.com/" rel="external">Testcontainers</a>, a library that lets you run throwaway, lightweight instances of common databases, web browsers, or any other application that can run in a Docker container. This allows you to easily integrate these dependencies into your automated tests, providing a consistent and reliable testing environment. By using Testcontainers, you can ensure that your tests are always running against a known and controlled version of your dependencies, avoiding inconsistencies and unexpected behavior while also simplifying test setup/teardown.</p>
<p>While Testcontainers provides the infrastructure, <a href="https://fauxrpc.com" rel="external">FauxRPC</a> takes care of the mocking itself. FauxRPC is a tool that generates fake gRPC, gRPC-Web, Connect, and REST servers from your Protobuf definitions. By combining it with Testcontainers, you gain a lightweight, isolated environment for testing your gRPC clients without relying on a real server implementation. I&rsquo;ve made a package to make this simpler using Go but the same could be done for other languages that Testcontainers supports.</p>
<h2 id="show-by-example">Show by Example</h2>
<h3 id="1-setting-up-the-container">1. Setting up the Container</h3>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>container<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> fauxrpctestcontainers<span style="color:#eceff4">.</span><span style="color:#88c0d0">Run</span><span style="color:#eceff4">(</span>ctx<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;docker.io/sudorandom/fauxrpc:latest&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ... error handling ...</span>
</span></span><span style="display:flex;"><span>t<span style="color:#eceff4">.</span><span style="color:#88c0d0">Cleanup</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span> container<span style="color:#eceff4">.</span><span style="color:#88c0d0">Terminate</span><span style="color:#eceff4">(</span>context<span style="color:#eceff4">.</span><span style="color:#88c0d0">Background</span><span style="color:#eceff4">())</span> <span style="color:#eceff4">})</span>
</span></span></code></pre></div><p>This snippet starts a FauxRPC container using the <code>fauxrpctestcontainers.Run</code> function. The <code>t.Cleanup</code> function ensures the container is terminated after the test, keeping your testing environment clean.</p>
<h3 id="2-registering-the-protobuf-definition">2. Registering the Protobuf Definition</h3>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>container<span style="color:#eceff4">.</span><span style="color:#88c0d0">MustAddFileDescriptor</span><span style="color:#eceff4">(</span>ctx<span style="color:#eceff4">,</span> elizav1<span style="color:#eceff4">.</span>File_connectrpc_eliza_v1_eliza_proto<span style="color:#eceff4">)</span>
</span></span></code></pre></div><p>You register your Protobuf file descriptor with the container. This lets FauxRPC understand the structure of your gRPC service. Now you have a fully functional FauxRPC service that mimics the services in the file descriptor that you gave it. The data is all randomly generated. Now let&rsquo;s test it a bit.</p>
<h3 id="3-making-grpc-calls">3. Making gRPC Calls</h3>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>baseURL <span style="color:#81a1c1">:=</span> container<span style="color:#eceff4">.</span><span style="color:#88c0d0">MustBaseURL</span><span style="color:#eceff4">(</span>ctx<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>elizaClient <span style="color:#81a1c1">:=</span> elizav1connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewElizaServiceClient</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>DefaultClient<span style="color:#eceff4">,</span> baseURL<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>resp<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> elizaClient<span style="color:#eceff4">.</span><span style="color:#88c0d0">Say</span><span style="color:#eceff4">(</span>ctx<span style="color:#eceff4">,</span> connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewRequest</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>elizav1<span style="color:#eceff4">.</span>SayRequest<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    Sentence<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;testing!&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}))</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// ... error handling and assertions ...</span>
</span></span></code></pre></div><p>This code is getting base URL of the FauxRPC server running in the container and creating a gRPC client (using ConnectRPC). ConnectRPC isn&rsquo;t a requirement. You can use grpc-go instead. With this client, you can make calls to your gRPC service as you would in a real environment. In this setup, FauxRPC automatically generates responses based on your Protobuf definitions. Here you would normally have some application logic that you want to test so this code might live elsewhere. The randomly generated data might work in a few scenarios but in order to test</p>
<h3 id="4-defining-stub-responses">4. Defining Stub Responses</h3>
<p>For more control over the responses you can define stubs. This allows you to simulate specific scenarios and test how your client handles different responses.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>container<span style="color:#eceff4">.</span><span style="color:#88c0d0">MustAddStub</span><span style="color:#eceff4">(</span>ctx<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;connectrpc.eliza.v1.ElizaService/Say&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">&amp;</span>elizav1<span style="color:#eceff4">.</span>SayResponse<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    Sentence<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;I am setting this text!&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">})</span>
</span></span></code></pre></div><h3 id="the-power-of-schemas-and-mocking">The power of schemas and mocking</h3>
<p>This is basically it. In these examples, I showed how you can:</p>
<ol>
<li>Stand up an empty FauxRPC service</li>
<li>Populate this server with some Protobuf schema</li>
<li>Connect and use this service</li>
<li>Set stub responses</li>
</ol>
<p>See full examples <a href="https://github.com/sudorandom/fauxrpc/blob/main/testcontainers/testcontainers_test.go" rel="external">in the FauxRPC repo</a>.</p>
<h2 id="benefits-of-using-fauxrpctestcontainers">Benefits of using FauxRPC+Testcontainers</h2>
<p>As demonstrated in the examples above, this approach simplifies gRPC testing by:</p>
<ul>
<li><strong>Simplified Testing:</strong> No need to set up a real gRPC server for testing.</li>
<li><strong>Isolated Environment:</strong> Each test runs in its own container, preventing conflicts and ensuring consistency.</li>
<li><strong>Increased Speed:</strong> Tests run faster due to the lightweight nature of containers.</li>
<li><strong>Improved Control:</strong>  Stubbing allows you to simulate various scenarios and edge cases.</li>
</ul>
<p>This package makes gRPC testing in Go much easier and more efficient. Give it a try and let me know what you think!</p>
<h2 id="alternatives">Alternatives</h2>
<p>In Go (and many targets for gRPC), you will get an interface that you can use to generate mock clients. Using the mock client you can usually set responses and assert on data from the request. This is a fair critique, and I feel like this strategy of testing could get you pretty far. However, there are a few reasons that FauxRPC+testcontainers is better.</p>
<p>First, using traditional mocking techniques will prevent you from testing your middleware. Maybe you have middleware that modifies the actual message in certain cases, returns an error, or performs some extra validation or accounting. With the FauxRPC+Testcontainers, you get to exercise the middleware code because you&rsquo;re talking to a real gRPC service.</p>
<p>In addition to that, maintaining and updating mock clients can be tedious as the gRPC API evolves. FauxRPC avoids this step by being dynamically configurable with protobuf descriptors.</p>
<p>Also, I like that this approach of using FauxRPC is language agnostic. Sure, the little library that makes it easier to use is written specifically in Go, but this code is very trivial to write for other languages.</p>
<p>Ultimately, the choice between mocking strategies depends on your specific needs and priorities.</p>
<h2 id="whats-next">What&rsquo;s Next</h2>
<p>Excited about the possibilities of FauxRPC Testcontainers? There&rsquo;s more to come! FauxRPC is still under active development and there&rsquo;s a lot more on the horizon! Here are a few features I&rsquo;m exploring:</p>
<ul>
<li>Rules using CEL: Fine-grained control over stub behavior using Common Expression Language (CEL) to define complex matching conditions and response generation logic. This will enable more dynamic and flexible stubbing scenarios. Imaging having a rule saying: <code>req.Name == &quot;Bob&quot;</code> then return a specific stub user.</li>
<li>Request Logging: Detailed logging of requests and responses to facilitate debugging and troubleshooting during test execution.</li>
<li>Improved documentation: I&rsquo;m currently working on a refresh of <a href="https://fauxrpc.com/" rel="external">fauxrpc.com</a> that includes these new features.</li>
</ul>
<p>Have an idea for a new feature or a suggestion for improvement? We&rsquo;d love to hear from you! <a href="https://github.com/sudorandom/fauxrpc/issues" rel="external">Open an issue</a> on the GitHub repository to share your thoughts or contribute to the project.</p>
<h3 id="references">References</h3>
<ul>
<li><strong>FauxRPC Testcontainers:</strong> <a href="https://github.com/sudorandom/fauxrpc/tree/main/testcontainers" rel="external">github.com/sudorandom/fauxrpc/testcontainers</a></li>
<li><strong>FauxRPC:</strong> <a href="https://fauxrpc.com" rel="external">fauxrpc.com</a></li>
<li><strong>Testcontainers:</strong> <a href="https://testcontainers.com" rel="external">testcontainers.com</a></li>
</ul>
]]></content:encoded></item><item><title>Self-Documenting Connect Services</title><link>https://kmcd.dev/posts/self-documenting-connect-services/</link><pubDate>Wed, 25 Sep 2024 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/self-documenting-connect-services/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/self-documenting-connect-services/cover_hu_6d1636dac10f3e09.webp" /> &lt;/p>
                
                gRPC can be pretty, too.
                </description><content:encoded><![CDATA[<p>As some of you may know, I&rsquo;ve created a plugin for protoc called <a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">protoc-gen-connect-openapi</a>. This plugin converts protobuf files into <a href="https://swagger.io/specification/" rel="external">OpenAPI specifications</a> for <a href="https://connectrpc.com/docs/protocol/" rel="external">the Connect protocol</a>. This protocol is very similar to gRPC but for unary RPCs it follows many more traditions that you&rsquo;d expect from an HTTP-based API, like using HTTP status codes appropriately, using the normal <code>Content-Encoding</code> header to specify compression and avoiding putting extra framing inside of the body. Because of this, we can document it more readily with other specifications like OpenAPI.</p>
<p>For more on the plugin itself, refer to <a href="https://kmcd.dev/posts/protoc-gen-connect-openapi/">my older post</a> that introduces protoc-gen-connect-openapi or <a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">the github repo</a> which has some more updates.</p>
<p>This post is about a new way to use this functionality, from <a href="https://pkg.go.dev/github.com/sudorandom/protoc-gen-connect-openapi/converter" rel="external">a Go library</a>. This Go API provides a simpler interface to generate OpenAPI from protobuf descriptors. You see, protoc plugins accept a <code>*pluginpb.CodeGeneratorRequest</code> and return a <code>*pluginpb.CodeGeneratorResponse</code>. The request type, in particular, is hard to use from Go. You have to encode every option you want to use into a single string. This is fine for a CLI but it&rsquo;s not very friendly for a Go library. But now that I made this library it is much easier to generate OpenAPI specs anywhere you run Go. Let&rsquo;s look at some examples.</p>
<h2 id="generating-openapi">Generating OpenAPI</h2>
<p>First, let&rsquo;s see how we can use this plugin to generate OpenAPI YAML from your protobuf definitions:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>openapiBody<span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">:=</span> converter<span style="color:#eceff4">.</span><span style="color:#88c0d0">GenerateSingle</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>    converter<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithGlobal</span><span style="color:#eceff4">(),</span>
</span></span><span style="display:flex;"><span>    converter<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithBaseOpenAPI</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">`
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">openapi: 3.1.0
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">info:
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">  title: OpenAPI Documentation of gRPC Services
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">  description: This is documentation that was generated from [protoc-gen-connect-openapi](https://github.com/sudorandom/protoc-gen-connect-openapi).
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">  version: 0.1.2
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">`</span><span style="color:#eceff4">)))</span>
</span></span><span style="display:flex;"><span>fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>openapiBody<span style="color:#eceff4">))</span>
</span></span></code></pre></div><p>With a few short lines, you now have OpenAPI YAML representation of your ConnectRPC services.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#81a1c1">openapi</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">3.1.0</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">info</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">title</span><span style="color:#eceff4">:</span> OpenAPI Documentation of gRPC Services
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">description</span><span style="color:#eceff4">:</span> This is documentation that was generated from [protoc-gen-connect-openapi](https://github.com/sudorandom/protoc-gen-connect-openapi).
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">paths</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">/connectrpc.eliza.v1.ElizaService/Say</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">post</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">tags</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        - connectrpc.eliza.v1.ElizaService
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">summary</span><span style="color:#eceff4">:</span> Say
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">operationId</span><span style="color:#eceff4">:</span> connectrpc.eliza.v1.ElizaService.Say
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">parameters</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#81a1c1">name</span><span style="color:#eceff4">:</span> Connect-Protocol-Version
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">in</span><span style="color:#eceff4">:</span> header
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">required</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">schema</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">$ref</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#39;#/components/schemas/connect-protocol-version&#39;</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#81a1c1">name</span><span style="color:#eceff4">:</span> Connect-Timeout-Ms
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">in</span><span style="color:#eceff4">:</span> header
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">schema</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">$ref</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#39;#/components/schemas/connect-timeout-header&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">requestBody</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1">content</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">application/json</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">schema</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>              <span style="color:#81a1c1">$ref</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#39;#/components/schemas/connectrpc.eliza.v1.SayRequest&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1">required</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">responses</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1">default</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">description</span><span style="color:#eceff4">:</span> Error
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">content</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">application/json</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>              <span style="color:#81a1c1">schema</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>                <span style="color:#81a1c1">$ref</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#39;#/components/schemas/connect.error&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#34;200&#34;</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">description</span><span style="color:#eceff4">:</span> Success
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">content</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">application/json</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>              <span style="color:#81a1c1">schema</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>                <span style="color:#81a1c1">$ref</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#39;#/components/schemas/connectrpc.eliza.v1.SayResponse&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">/connectrpc.eliza.v1.ElizaService/Converse</span><span style="color:#eceff4">:</span> {}
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">/connectrpc.eliza.v1.ElizaService/Introduce</span><span style="color:#eceff4">:</span> {}
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">components</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">schemas</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">connectrpc.eliza.v1.SayRequest</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">type</span><span style="color:#eceff4">:</span> object
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">properties</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1">sentence</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">type</span><span style="color:#eceff4">:</span> string
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">title</span><span style="color:#eceff4">:</span> sentence
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">title</span><span style="color:#eceff4">:</span> SayRequest
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">additionalProperties</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">connectrpc.eliza.v1.SayResponse</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">type</span><span style="color:#eceff4">:</span> object
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">properties</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1">sentence</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">type</span><span style="color:#eceff4">:</span> string
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">title</span><span style="color:#eceff4">:</span> sentence
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">title</span><span style="color:#eceff4">:</span> SayResponse
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">additionalProperties</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">connect-protocol-version</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">type</span><span style="color:#eceff4">:</span> number
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">title</span><span style="color:#eceff4">:</span> Connect-Protocol-Version
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">enum</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">description</span><span style="color:#eceff4">:</span> Define the version of the Connect protocol
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">const</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">connect-timeout-header</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">type</span><span style="color:#eceff4">:</span> number
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">title</span><span style="color:#eceff4">:</span> Connect-Timeout-Ms
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">description</span><span style="color:#eceff4">:</span> Define the timeout, in ms
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">connect.error</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">type</span><span style="color:#eceff4">:</span> object
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">properties</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1">code</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">type</span><span style="color:#eceff4">:</span> string
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">examples</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>            - CodeNotFound
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">enum</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>            - CodeCanceled
</span></span><span style="display:flex;"><span>            - CodeUnknown
</span></span><span style="display:flex;"><span>            - CodeInvalidArgument
</span></span><span style="display:flex;"><span>            - CodeDeadlineExceeded
</span></span><span style="display:flex;"><span>            - CodeNotFound
</span></span><span style="display:flex;"><span>            - CodeAlreadyExists
</span></span><span style="display:flex;"><span>            - CodePermissionDenied
</span></span><span style="display:flex;"><span>            - CodeResourceExhausted
</span></span><span style="display:flex;"><span>            - CodeFailedPrecondition
</span></span><span style="display:flex;"><span>            - CodeAborted
</span></span><span style="display:flex;"><span>            - CodeOutOfRange
</span></span><span style="display:flex;"><span>            - CodeInternal
</span></span><span style="display:flex;"><span>            - CodeUnavailable
</span></span><span style="display:flex;"><span>            - CodeDataLoss
</span></span><span style="display:flex;"><span>            - CodeUnauthenticated
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">description</span><span style="color:#eceff4">:</span> The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1">message</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">type</span><span style="color:#eceff4">:</span> string
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">description</span><span style="color:#eceff4">:</span> A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1">detail</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">$ref</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#39;#/components/schemas/google.protobuf.Any&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">title</span><span style="color:#eceff4">:</span> Connect Error
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">additionalProperties</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">description: &#39;Error type returned by Connect</span><span style="color:#eceff4">:</span> https://connectrpc.com/docs/go/errors/#http-representation&#39;
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">google.protobuf.Any</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">type</span><span style="color:#eceff4">:</span> object
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">properties</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1">type</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">type</span><span style="color:#eceff4">:</span> string
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1">value</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">type</span><span style="color:#eceff4">:</span> string
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">format</span><span style="color:#eceff4">:</span> binary
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1">debug</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">type</span><span style="color:#eceff4">:</span> object
</span></span><span style="display:flex;"><span>          <span style="color:#81a1c1">additionalProperties</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">additionalProperties</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">description</span><span style="color:#eceff4">:</span> Contains an arbitrary serialized message along with a @type that describes the type of the serialized message.
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">security</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">tags</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#81a1c1">name</span><span style="color:#eceff4">:</span> connectrpc.eliza.v1.ElizaService
</span></span></code></pre></div><p>This file is truncated to only show a single endpoint and related types. To see the full file, <a href="https://kmcd.dev/posts/self-documenting-connect-services/openapi.yaml">click here</a>. The <code>converter.WithGlobal()</code> option uses the global protobuf registry as the source. If you want to use specific (or protobuf file descriptors not in that registry), you can pass in any <code>protoregistry.GlobalFiles</code> value to the <code>converter.WithFiles()</code> option. With <code>converter.WithBaseOpenAPI()</code>, you can specify a base OpenAPI spec that will be used as the basis for the generated one. Here you can add a description, version, security schemes, link to other documentation, etc.</p>
<p>But what&rsquo;s the practical use of this YAML?</p>
<h2 id="show-the-world-what-you-can-do">Show the world what you can do</h2>
<p>With a few additional lines of code, we can leverage one of the numerous OpenAPI documentation visualization tools to transform this YAML into a visually appealing and interactive web page. Here&rsquo;s an example using Elements from Spotlight:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> tmplElements <span style="color:#eceff4">=</span> template<span style="color:#eceff4">.</span><span style="color:#88c0d0">Must</span><span style="color:#eceff4">(</span>template<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;name&#34;</span><span style="color:#eceff4">).</span><span style="color:#88c0d0">Parse</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">`&lt;!doctype html&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">&lt;html lang=&#34;en&#34;&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	&lt;head&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	&lt;meta charset=&#34;utf-8&#34;&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	&lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1, shrink-to-fit=no&#34;&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	&lt;title&gt;OpenAPI Documentation&lt;/title&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	&lt;script src=&#34;https://unpkg.com/@stoplight/elements@8.3.4/web-components.min.js&#34;&gt;&lt;/script&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	&lt;link rel=&#34;stylesheet&#34; href=&#34;https://unpkg.com/@stoplight/elements@8.3.4/styles.min.css&#34;&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	&lt;/head&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	&lt;body&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	&lt;elements-api
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">		id=&#34;docs&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">		router=&#34;hash&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">		layout=&#34;sidebar&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	/&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	&lt;script&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	(async () =&gt; {
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">		const docs = document.getElementById(&#39;docs&#39;);
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">		docs.apiDescriptionDocument = atob(&#34;</span><span style="color:#5e81ac;font-style:italic">{{</span> <span style="color:#8fbcbb">.DocumentBase64</span> <span style="color:#5e81ac;font-style:italic">}}</span><span style="color:#a3be8c">&#34;);
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	})();
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	&lt;/script&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">	&lt;/body&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">&lt;/html&gt;`</span><span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	mux <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServeMux</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">Handle</span><span style="color:#eceff4">(</span>elizav1connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewElizaServiceHandler</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>elizav1connect<span style="color:#eceff4">.</span>UnimplementedElizaServiceHandler<span style="color:#eceff4">{}))</span>
</span></span><span style="display:flex;"><span>	openapiBody<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> converter<span style="color:#eceff4">.</span><span style="color:#88c0d0">GenerateSingle</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>		converter<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithGlobal</span><span style="color:#eceff4">(),</span>
</span></span><span style="display:flex;"><span>		converter<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithContentTypes</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>			<span style="color:#a3be8c">&#34;json&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			<span style="color:#a3be8c">&#34;proto&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>		converter<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithStreaming</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>        converter<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithAllowGET</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>		converter<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithBaseOpenAPI</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">`
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">openapi: 3.1.0
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">info:
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">  title: OpenAPI Documentation of gRPC Services
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">  description: This is documentation that was generated from [protoc-gen-connect-openapi](https://github.com/sudorandom/protoc-gen-connect-openapi).
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">`</span><span style="color:#eceff4">)))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;err: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	generationTime <span style="color:#81a1c1">:=</span> time<span style="color:#eceff4">.</span><span style="color:#88c0d0">Now</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">Handle</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;GET /openapi.html&#34;</span><span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandlerFunc</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> tmplElements<span style="color:#eceff4">.</span><span style="color:#88c0d0">Execute</span><span style="color:#eceff4">(</span>w<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">struct</span><span style="color:#eceff4">{</span> DocumentBase64 <span style="color:#81a1c1">string</span> <span style="color:#eceff4">}{</span>
</span></span><span style="display:flex;"><span>			DocumentBase64<span style="color:#eceff4">:</span> base64<span style="color:#eceff4">.</span>StdEncoding<span style="color:#eceff4">.</span><span style="color:#88c0d0">EncodeToString</span><span style="color:#eceff4">(</span>openapiBody<span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">});</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Error</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;rendering_template&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;error&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}))</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">Handle</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;GET /openapi.yaml&#34;</span><span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandlerFunc</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		http<span style="color:#eceff4">.</span><span style="color:#88c0d0">ServeContent</span><span style="color:#eceff4">(</span>w<span style="color:#eceff4">,</span> r<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;openapi.yaml&#34;</span><span style="color:#eceff4">,</span> generationTime<span style="color:#eceff4">,</span> bytes<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>openapiBody<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	addr <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;127.0.0.1:6660&#34;</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Starting connectrpc on http://%s&#34;</span><span style="color:#eceff4">,</span> addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;OpenAPI Doc Page http://%s/openapi.html&#34;</span><span style="color:#eceff4">,</span> addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;OpenAPI Spec http://%s/openapi.yaml&#34;</span><span style="color:#eceff4">,</span> addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	srv <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span>Server<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Addr<span style="color:#eceff4">:</span>    addr<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		Handler<span style="color:#eceff4">:</span> mux<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> srv<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServe</span><span style="color:#eceff4">();</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;error: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>And here&rsquo;s what it looks like whenever you hit <code>http://127.0.0.1.:6660/openapi.html</code> in a web browser:</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/self-documenting-connect-services/openapi-sshot_hu_8a1f37f0a5e89e2e.webp" class="center" width="800px"/>
    


<p>To see the demo for yourself, <a href="https://kmcd.dev/posts/self-documenting-connect-services/openapi.html">click here!</a></p>
<p>In this example, we&rsquo;re using the <a href="https://www.npmjs.com/package/@stoplight/elements" rel="external">@stoplight/elements</a> library to render the OpenAPI documentation. The <code>tmplElements</code> template embeds the generated OpenAPI YAML (Base64 encoded) into an HTML page, providing a user-friendly interface to explore your API&rsquo;s endpoints, request/response structures, and more. Note that we&rsquo;re using base64 so that none of the YAML characters accidentally escape the javascript string and mess everything up for us. You can also have the script load the OpenAPI spec from a URL, which is what <a href="https://github.com/stoplightio/elements?tab=readme-ov-file#web-component" rel="external">many of the examples show</a>.</p>
<p>You may also notice some additional options in this example, like <code>converter.WithContentTypes()</code>, <code>converter.WithStreaming(true)</code> and <code>converter.WithAllowGET(true)</code>. These give you more control over content types, whether you want OpenAPI for streaming calls (which may be complicated to support for OpenAPI) and whether you want to generate documentation for GET requests, that Connect supports if you can set the <code>idempotency_level</code> option to <code>NO_SIDE_EFFECTS</code>. For more information on GET requests and a comprehensive list of available options, refer to the <a href="https://connectrpc.com/docs/go/get-requests-and-caching/" rel="external">the Connect documentation</a> and the <a href="https://pkg.go.dev/github.com/sudorandom/protoc-gen-connect-openapi/converter" rel="external">protoc-gen-connect-openapi Go documentation</a>, respectively.</p>
<h2 id="benefits-of-self-documenting-services">Benefits of Self-Documenting Services</h2>
<p>Clear and interactive documentation makes it easier for developers to understand and integrate with your APIs. This reduces the friction between teams and gives you something to point to in case there are questions about the API. Not everyone is fluent in reading protobuf, but HTTP documentation is much friendlier.</p>
<p>By generating documentation from protobufs, it is much easier for documentation to stay in sync with your codebase. Note that adding comments to your protobuf types, fields, services and methods also get carried over to this OpenAPI specification (and the generated protobuf/gRPC/gRPC-Web/Connect source code) so protobuf files can act as a single place to document everything about that service.</p>
<h2 id="conclusion">Conclusion</h2>
<p>By combining the power of protoc-gen-connect-openapi with OpenAPI visualization tools, you can effortlessly generate self-documenting Connect services. This approach streamlines development, fosters collaboration, and empowers developers to consume your APIs effectively.</p>
]]></content:encoded></item><item><title>gRPC Over HTTP/3: Followup</title><link>https://kmcd.dev/posts/grpc-over-http3-followup/</link><pubDate>Tue, 17 Sep 2024 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/grpc-over-http3-followup/</guid><description><![CDATA[ 
                <p> <img hspace="5" src="https://kmcd.dev/posts/grpc-over-http3-followup/cover_hu_efe0cb4d467586cd.webp" /> </p>
                
                Let&#39;s cover some recent updates!
                ]]></description><content:encoded><![CDATA[<p>In my previous post, &ldquo;<a href="https://kmcd.dev/posts/grpc-over-http3/">gRPC Over HTTP/3</a>,&rdquo; we explored the potential of gRPC with HTTP/3. At that time, some of the pieces were missing and I had to hack on forks of a few repos to make gRPC+HTTP/3 work with Go. The biggest blocker was that the quic-go HTTP/3 implementation didn&rsquo;t have support for HTTP trailers. But now things have recently changed there and these hacks are no longer needed!</p>
<h3 id="quic-go-now-supports-http-trailers">quic-go now supports HTTP Trailers</h3>
<p>If you recall, this was a major roadblock for getting gRPC to work over HTTP/3. Trailers are crucial for gRPC&rsquo;s error handling and status codes, so this was a big deal. This works as of <a href="https://github.com/quic-go/quic-go/releases/tag/v0.47.0" rel="external">v0.47.0</a>. Here are the related PRs:</p>
<ul>
<li><a href="https://github.com/quic-go/quic-go/pull/4581" rel="external">https://github.com/quic-go/quic-go/pull/4581</a> - client support for trailers</li>
<li><a href="https://github.com/quic-go/quic-go/pull/4630" rel="external">https://github.com/quic-go/quic-go/pull/4630</a> - server support for trailers</li>
<li><a href="https://github.com/quic-go/quic-go/pull/4656" rel="external">https://github.com/quic-go/quic-go/pull/4656</a> - using declared trailer names for empty trailer entries at the start of the request</li>
<li><a href="https://github.com/quic-go/quic-go/pull/4639" rel="external">https://github.com/quic-go/quic-go/pull/4639</a> - disallow pseudo-headers from being used as a trailer</li>
</ul>
<p>As you can see, I&rsquo;ve contributed most of the work for this!</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-over-http3-followup/quic-go_hu_be5110c63da3e7bd.webp" class="center" width="400px"/>
    


<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">go</span> get <span style="color:#81a1c1">-</span>u github<span style="color:#eceff4">.</span>com<span style="color:#81a1c1">/</span>quic<span style="color:#81a1c1">-</span><span style="color:#81a1c1;font-weight:bold">go</span><span style="color:#81a1c1">/</span>quic<span style="color:#81a1c1">-</span><span style="color:#81a1c1;font-weight:bold">go</span>
</span></span></code></pre></div><p>This now enables HTTP/3 support for ConnectRPC for gRPC, gRPC-Web and Connect for both client and server. While I&rsquo;d recommend deploying in production with a more established load balancer that supports HTTP/3, <code>quic-go</code> is perfect for experimentation and development. I&rsquo;ll show you how to set up Go + HTTP/3 (via quic-go) + Connect a bit further down in this article.</p>
<h3 id="bufs-curl-command-has-a-new---http3-flag">Buf&rsquo;s curl command has a new <code>--http3</code> flag</h3>
<p>That&rsquo;s right, you can now easily test your gRPC services over HTTP/3 from the command line. This is a fantastic development for quick prototyping, debugging and having a simple tool to call gRPC services. You can use this as of <a href="https://github.com/bufbuild/buf/releases/tag/v1.41.0" rel="external">v1.41.0</a>. Here are the related PRs:</p>
<ul>
<li><a href="https://github.com/bufbuild/buf/pull/3127" rel="external">https://github.com/bufbuild/buf/pull/3127</a> - Add <code>--http3</code> flag for gRPC-Web and Connect</li>
<li><a href="https://github.com/bufbuild/buf/pull/3305" rel="external">https://github.com/bufbuild/buf/pull/3305</a> - Add <code>--http3</code> flag for gRPC</li>
</ul>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-over-http3-followup/buf-curl_hu_27758b08c80454d9.webp" class="center" width="500px"/>
    


<p><a href="https://buf.build/docs/installation" rel="external">Upgrade</a> to the new version today!</p>
<h3 id="open-source">Open Source</h3>
<p>I&rsquo;m very happy to have contributed both of these features. Like I said in my <a href="https://kmcd.dev/posts/grpc-over-http3/">previous post about this topic</a>, the Go version of ConnectRPC seemed <em>so close</em> to having full HTTP/3 support in all three protocols: Connect/gRPC-Web and the original gRPC; it just needed trailer support to push gRPC over the finish line. And with other gRPC implementations, like <a href="https://devblogs.microsoft.com/dotnet/http-3-support-in-dotnet-6/" rel="external">grpc-dotnet</a>, I hope the addition of HTTP/3 to <code>buf curl</code> command can be useful as well.</p>
<h3 id="what-does-this-mean-for-you">What does this mean for you?</h3>
<p>In short, it means that if you&rsquo;re working on a gRPC project, it&rsquo;s now slightly more viable to use HTTP/3 <em>today</em>&hellip; in specific contexts. Here&rsquo;s a recap of the benefits:</p>
<ul>
<li><strong>Faster connections, especially on unreliable mobile connections:</strong> HTTP/3&rsquo;s connection setup is lightning-fast compared to HTTP/2, and it handles flaky networks like a champ. This is a major win for mobile apps and any situation where network conditions aren&rsquo;t ideal. This can be useful when you&rsquo;re using gRPC-Web or Connect on the frontend.</li>
<li><strong>No more head-of-line blocking:</strong> HTTP/3 eliminates this pesky problem that can slow down HTTP/2 in certain scenarios. If your gRPC service handles lots of concurrent streams, you might see an improvement.</li>
</ul>
<h3 id="trying-it-out-with-connectrpc">Trying it out with ConnectRPC</h3>
<p>Here&rsquo;s an example of starting a HTTP/3 server with ConnectRPC:
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	mux <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServeMux</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Implementation is only in the full source</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">Handle</span><span style="color:#eceff4">(</span>elizav1connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewElizaServiceHandler</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>server<span style="color:#eceff4">{}))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	addr <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;127.0.0.1:6660&#34;</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Starting connectrpc on %s&#34;</span><span style="color:#eceff4">,</span> addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	h3srv <span style="color:#81a1c1">:=</span> http3<span style="color:#eceff4">.</span>Server<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Addr<span style="color:#eceff4">:</span>    addr<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		Handler<span style="color:#eceff4">:</span> mux<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> h3srv<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServeTLS</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;cert.crt&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;cert.key&#34;</span><span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;error: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span></span></span></code></pre></div></p>
<aside>
<a href="https://github.com/sudorandom/example-connect-http3/blob/v0.0.2/server-single/main.go" target="_blank">See the full source at GitHub.</a>

</aside>

<p>This example uses the HTTP/3 server from quic-go to provide HTTP/3. Now you can test it using <code>buf curl</code>. Here&rsquo;s an example of using <code>buf curl</code> with gRPC:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl --http3 -k <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --protocol<span style="color:#81a1c1">=</span>grpc <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --schema<span style="color:#81a1c1">=</span>buf.build/connectrpc/eliza <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  -d <span style="color:#a3be8c">&#39;{&#34;sentence&#34;:&#34;Hello, with gRPC+h3&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  https://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;sentence&#34;</span>: <span style="color:#a3be8c">&#34;Hello, with gRPC+h3&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>With gRPC-Web:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl --http3 -k <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --protocol<span style="color:#81a1c1">=</span>grpcweb <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --schema<span style="color:#81a1c1">=</span>buf.build/connectrpc/eliza <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  -d <span style="color:#a3be8c">&#39;{&#34;sentence&#34;:&#34;Hello, with gRPC-Web+h3&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  https://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;sentence&#34;</span>: <span style="color:#a3be8c">&#34;Hello, with gRPC-Web+h3&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>With Connect:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl --http3 -k <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --protocol<span style="color:#81a1c1">=</span>connect <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --schema<span style="color:#81a1c1">=</span>buf.build/connectrpc/eliza <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  -d <span style="color:#a3be8c">&#39;{&#34;sentence&#34;:&#34;Hello, with Connect+h3&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  https://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;sentence&#34;</span>: <span style="color:#a3be8c">&#34;Hello, with Connect+h3&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>Note that if you don&rsquo;t use the <code>--http3</code> flag this doesn&rsquo;t work. That&rsquo;s because we&rsquo;ve only started an HTTP/3 server. Using the following code, we can run HTTP/3 alongside HTTP/1.1 and HTTP/2:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	mux <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServeMux</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">Handle</span><span style="color:#eceff4">(</span>elizav1connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewElizaServiceHandler</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>server<span style="color:#eceff4">{}))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	addr <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;127.0.0.1:6660&#34;</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Starting connectrpc on %s&#34;</span><span style="color:#eceff4">,</span> addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	h3srv <span style="color:#81a1c1">:=</span> http3<span style="color:#eceff4">.</span>Server<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Addr<span style="color:#eceff4">:</span>    addr<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		Handler<span style="color:#eceff4">:</span> mux<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	srv <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span>Server<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Addr<span style="color:#eceff4">:</span>    addr<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		Handler<span style="color:#eceff4">:</span> h2c<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewHandler</span><span style="color:#eceff4">(</span>mux<span style="color:#eceff4">,</span> <span style="color:#81a1c1">&amp;</span>http2<span style="color:#eceff4">.</span>Server<span style="color:#eceff4">{}),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	eg<span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">:=</span> errgroup<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithContext</span><span style="color:#eceff4">(</span>context<span style="color:#eceff4">.</span><span style="color:#88c0d0">Background</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>	eg<span style="color:#eceff4">.</span><span style="color:#88c0d0">Go</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> h3srv<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServeTLS</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;cert.crt&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;cert.key&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>	eg<span style="color:#eceff4">.</span><span style="color:#88c0d0">Go</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> srv<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServeTLS</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;cert.crt&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;cert.key&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> eg<span style="color:#eceff4">.</span><span style="color:#88c0d0">Wait</span><span style="color:#eceff4">();</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;error: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><aside>
<a href="https://github.com/sudorandom/example-connect-http3/blob/v0.0.2/server-multi/main.go" target="_blank">See the full source at GitHub.</a>

</aside>

<p>With this code, you can now connect using any version of HTTP and with gRPC, gRPC-Web or Connect.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl -k --protocol<span style="color:#81a1c1">=</span>grpc <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --schema<span style="color:#81a1c1">=</span>buf.build/connectrpc/eliza <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  -d <span style="color:#a3be8c">&#39;{&#34;sentence&#34;:&#34;Hello, with gRPC+h2&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  https://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;sentence&#34;</span>: <span style="color:#a3be8c">&#34;Hello, with gRPC+h2&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>The compatibility matrix is now all green when using ConnectRPC, with only a single exception (because HTTP/1.1 servers and clients typically don&rsquo;t support HTTP trailers):</p>
<table>
  <thead>
      <tr>
          <th>Protocol</th>
          <th>HTTP/1.1</th>
          <th>HTTP/2</th>
          <th>HTTP/3</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md" rel="external">gRPC</a></td>
          <td>❌</td>
          <td>✅</td>
          <td>✅</td>
      </tr>
      <tr>
          <td><a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md" rel="external">gRPC-Web</a></td>
          <td>✅</td>
          <td>✅</td>
          <td>✅</td>
      </tr>
      <tr>
          <td><a href="https://connectrpc.com/docs/protocol/" rel="external">Connect</a></td>
          <td>✅</td>
          <td>✅</td>
          <td>✅</td>
      </tr>
  </tbody>
</table>
<p>See the repo at <a href="https://github.com/sudorandom/example-connect-http3/" rel="external">sudorandom/example-connect-http3</a> to see the full examples shown here as well as some example client code.</p>
<h2 id="so-everything-is-fast-with-this-right">So everything is fast with this, right?</h2>
<p><strong>Well, no.</strong> HTTP/3 isn&rsquo;t always a performance win&hellip; and actually, today, it may generally be slower or, at best, the same speed as HTTP/2. Part of the cause is that it uses a lot of CPU cycles compared to HTTP/1.1 and HTTP/2. You might be asking: &ldquo;this awesome protocol that is supposed to make things fast is <em>actually slower</em>? What&rsquo;s the point?&rdquo;. This is a good question that&rsquo;s been answered many times.</p>
<p>QUIC is still mostly implemented in user-space and is lacking the half-century of optimizations that TCP has had. I recently saw <a href="https://dl.acm.org/doi/10.1145/3589334.3645323" rel="external">this paper</a> which looks to be some decent data regarding actual HTTP/3 performance. Generally, it&rsquo;s not a good story for QUIC or HTTP/3.</p>
<p>Just for completeness, here are some other testimonies of the performance of HTTP/3 and QUIC:</p>
<ul>
<li><a href="https://dl.acm.org/doi/10.1145/3589334.3645323" rel="external">https://dl.acm.org/doi/10.1145/3589334.3645323</a> (2024)</li>
<li><a href="https://daniel.haxx.se/blog/2024/06/10/http-3-in-curl-mid-2024/comment-page-1/" rel="external">https://daniel.haxx.se/blog/2024/06/10/http-3-in-curl-mid-2024/comment-page-1/</a> (2024)</li>
<li><a href="https://www.cloudpanel.io/blog/http3-vs-http2/" rel="external">https://www.cloudpanel.io/blog/http3-vs-http2/</a> (2024)</li>
<li><a href="https://tailscale.com/blog/quic-udp-throughput" rel="external">https://tailscale.com/blog/quic-udp-throughput</a> (2023)</li>
<li><a href="https://pulse.internetsociety.org/blog/measuring-http-3-real-world-performance" rel="external">https://pulse.internetsociety.org/blog/measuring-http-3-real-world-performance</a> (2023)</li>
<li><a href="https://pulse.internetsociety.org/blog/the-challenges-ahead-for-http-3" rel="external">https://pulse.internetsociety.org/blog/the-challenges-ahead-for-http-3</a> (2023)</li>
<li><a href="https://dropbox.tech/frontend/investigating-the-impact-of-http3-on-network-latency-for-search" rel="external">https://dropbox.tech/frontend/investigating-the-impact-of-http3-on-network-latency-for-search</a> (2023)</li>
<li><a href="https://x.com/alonkochba/status/1424403252284694528" rel="external">https://x.com/alonkochba/status/1424403252284694528</a> (2021)</li>
<li><a href="https://blog.cloudflare.com/http-3-vs-http-2/" rel="external">https://blog.cloudflare.com/http-3-vs-http-2/</a> (2020)</li>
<li><a href="https://dl.acm.org/doi/pdf/10.1145/3098822.3098842" rel="external">https://dl.acm.org/doi/pdf/10.1145/3098822.3098842</a> (2017)</li>
</ul>
<p>The results are mixed, but it generally indicates that the receiver end needs more optimizations. Specifically, the proposed solutions involve a technique called UDP generic receive offload (UDP GRO). Some experiments with these kinds of optimizations have shown very promising results. And you do have to remember that the number of round trips to establish a connection being reduced is, at its conceptual level, a winning strategy. The only thing stopping world domination is the pesky details.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-over-http3-followup/the-quic-we-were-promised_hu_cae21d4c943d4f2c.webp" class="center" width="500px"/>
    


<p> </p>
<h3 id="however-theres-reason-for-optimism">However, there&rsquo;s reason for optimism</h3>
<p>The tooling surrounding HTTP/3 <em>is</em> rapidly maturing, which is significantly lowering the barrier to entry for developers eager to experiment and adopt this technology early on. With libraries like <code>quic-go</code> now offering comprehensive support for essential features like HTTP Trailers and tools like <code>buf curl</code> providing seamless testing capabilities, the path to integrating HTTP/3 into your gRPC projects is smoother than ever.</p>
<p>Additionally, performance optimizations are actively being researched and implemented. Beyond tooling, the performance landscape for HTTP/3 is far from stagnant. Active research and development are focused on optimizing QUIC implementations, particularly on the receiver side. Promising techniques like UDP Generic Receive Offload (GRO) show the potential to significantly enhance HTTP/3&rsquo;s efficiency and responsiveness.</p>
<p>Even today, HTTP/3 and QUIC have their niches that are pretty compelling. Specifically, HTTP/3 consistently does pretty well with reducing the number of pauses with video conferencing and video streaming while improving general web usage with slow/unstable networks, typically with mobile devices.</p>
<h2 id="thanks-all">Thanks all</h2>
<p>With quic-go&rsquo;s new support for HTTP trailers and <code>buf curl</code>&rsquo;s new HTTP/3 flag, experimenting with gRPC over HTTP/3 is now easier than ever. I challenge you to try out gRPC over HTTP/3 in your own projects and share your experiences. I want to help build a community pushing gRPC and protobufs into more places, and this is a small part of that.</p>
]]></content:encoded></item><item><title>JSON to Protobuf Conversion</title><link>https://kmcd.dev/posts/json-to-proto/</link><pubDate>Tue, 10 Sep 2024 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/json-to-proto/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/json-to-proto/cover_hu_5d77c665ee556aba.webp" /> &lt;/p>
                
                Deep dive into a small Protobuf tool
                </description><content:encoded><![CDATA[<p>In the world of data serialization and communication protocols, protocol buffers (protobuf) have gained a lot popularity due to their efficiency and performance advantages over formats like JSON and the usage of gRPC. However, the learning curve associated with protobuf&rsquo;s syntax and concepts can be a deterrent for developers, especially those already comfortable with the ubiquitous JSON format. On top of that, converting a lot of APIs from JSON to protobuf can be really time-consuming.</p>
<p>Enter the <a href="https://json-to-proto.github.io/" rel="external">JSON-to-Proto</a>, a tool that aims to bridge this gap by automatically translating JSON data structures into their protobuf equivalents. While this can offer a seemingly straightforward entry point to protobuf adoption, it&rsquo;s important to understand both the potential benefits <em>and the limitations</em> that come with such an approach.</p>
<p>The tool is located here: <a href="https://json-to-proto.github.io/" rel="external">json-to-proto.github.io</a>.</p>
<h2 id="potential-use-cases"><strong>Potential use cases</strong></h2>
<p>Here are some situations where you might use this tool:</p>
<ul>
<li>
<p><strong>Rapid prototyping:</strong> When experimenting with a new project or feature, the converter enables developers to quickly generate protobuf schemas from existing JSON data structures, which <em>may</em> accelerate the initial development cycle.</p>
</li>
<li>
<p><strong>Legacy system migration:</strong> For organizations transitioning from JSON-based systems to protobuf, the converter can ease the migration process by automating the initial conversion.</p>
</li>
<li>
<p><strong>API design exploration:</strong> During the API design phase, the converter can be used to explore different protobuf schema representations based on sample JSON requests and responses, facilitating discussions and decision-making.</p>
</li>
<li>
<p><strong>Educational purposes:</strong> The converter can serve as a valuable learning tool, allowing developers to visualize how JSON structures translate into protobuf schemas, aiding their understanding of protobuf concepts.</p>
</li>
</ul>
<p>It&rsquo;s crucial to remember that the converter is most effective when used strategically in conjunction with a growing understanding of protobuf. While it can provide a helpful starting point, developers should always strive to refine the generated schemas and leverage protobuf&rsquo;s full capabilities for optimal performance and maintainability. There are many protobuf features that do not have a corollary in JSON, so try to take advantage of more protobuf&rsquo;s features.</p>
<h2 id="getting-it-right">Getting it right</h2>
<p>One place I like to go to for a typical REST API is the swagger petstore example, <a href="https://petstore.swagger.io" rel="external">available at petstore.swagger.io</a>. This API acts an example for OpenAPI, so it has a mix of features that you&rsquo;ll see when using a typical REST API. So let&rsquo;s what JSON-to-Proto will do with this API.</p>
<p>Starting with this example of a single &ldquo;Pet&rdquo; object:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;id&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;category&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;id&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;name&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;string&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;name&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;doggie&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;photoUrls&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;string&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;tags&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;id&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#81a1c1">&#34;name&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;string&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;status&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;available&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>After pasting into the tool, I get the following protobuf back:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">SomeMessage</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">Category</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>        <span style="color:#81a1c1">uint32</span> id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>        <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">Tags</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>        <span style="color:#81a1c1">uint32</span> id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>        <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1">uint32</span> id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    Category category <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">3</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">repeated</span> <span style="color:#81a1c1">string</span> photo_urls <span style="color:#81a1c1">=</span> <span style="color:#b48ead">4</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">repeated</span> Tags tags <span style="color:#81a1c1">=</span> <span style="color:#b48ead">5</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1">string</span> status <span style="color:#81a1c1">=</span> <span style="color:#b48ead">6</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>This is pretty decent. It made some assumptions that <code>uint32</code> is what all IDs look like, which may be fair but also may be wrong. Now that you&rsquo;ve seen one example, let&rsquo;s talk about ways this tool could lead us astray.</p>
<h2 id="the-price-of-simplicity">The Price of Simplicity</h2>
<h3 id="loss-of-expressiveness">Loss of Expressiveness</h3>
<p>JSON, while flexible, lacks the type richness and structural constraints of protobuf. This means that converting JSON to protobuf can result in a loss of information and reduced clarity.</p>
<ul>
<li><strong>Enums</strong>: Protobuf&rsquo;s enums provide a clear way to define a restricted set of values. JSON, lacking this concept, forces converters to rely on string representations (or maybe numeric?), which are less type-safe and can lead to runtime errors.</li>
<li><strong>Numeric Types</strong>: JSON&rsquo;s &ldquo;number&rdquo; type is ambiguous, encompassing a wide range of numeric representations. Protobuf, on the other hand, offers a selection of specific integer and floating-point types, enabling more efficient storage and processing. Converters may struggle to accurately infer the intended protobuf type from a JSON number.</li>
<li><strong>Maps</strong>: Converters probably can&rsquo;t pick up on a field that has arbitrary key/value pairs. Since everything is a &ldquo;JSON Object&rdquo; it&rsquo;s hard to tell the difference between a <code>map&lt;string, string&gt;</code> and <code>message { ... }</code>.</li>
<li><strong>oneOf</strong>; This tool doesn&rsquo;t detect situations where <code>oneOf</code> would be useful.</li>
</ul>
<h3 id="imperfect-conversions">Imperfect Conversions</h3>
<p>The complexity of data structures and the ambiguity inherent in JSON can lead to conversion errors or less-than-ideal protobuf schemas.</p>
<ul>
<li><strong>Ambiguous input, ambiguous output</strong>: If your examples leave a field as &ldquo;null&rdquo; then you may end up with <code>google.protobuf.Any</code> types, which is usually not what you want. The example data should always be &ldquo;complete&rdquo; to avoid this behavior.</li>
<li><strong>One field, two types</strong>: When a JSON field can hold values of different types, converters might default to using <code>google.protobuf.Any</code>, a generic container type that can encapsulate any protobuf message. While convenient, this approach sacrifices type safety and can make working with the resulting protobuf data more cumbersome.</li>
</ul>
<h2 id="getting-it-wrong">Getting it wrong</h2>
<p>Let&rsquo;s look at some concrete examples from JSON-to-proto to see where it can get things wrong and what you should do to correct it.</p>
<h3 id="incomplete-examples">Incomplete examples</h3>
<p>If any fields are excluded or set to <code>null</code>, JSON-to-proto will use <code>google.protobuf.Any</code> as the type. This is almost always the wrong answer:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;id&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1234</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;name&#34;</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">null</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>yields</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#a3be8c">&#34;google/protobuf/any.proto&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">SomeMessage</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1">uint32</span> id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    google.protobuf.Any name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><h3 id="multi-typed-fields">Multi-typed Fields</h3>
<p>Here&rsquo;s an example where the ID for users could be a string or an integer:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;users&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">{</span><span style="color:#81a1c1">&#34;type&#34;</span><span style="color:#eceff4">:</span><span style="color:#a3be8c">&#34;admin&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">&#34;id&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;admin-1&#34;</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">{</span><span style="color:#81a1c1">&#34;type&#34;</span><span style="color:#eceff4">:</span><span style="color:#a3be8c">&#34;user&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">&#34;id&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1234</span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>and here is the resulting protobuf:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">SomeMessage</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">Users</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>        <span style="color:#81a1c1">string</span> type <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>        <span style="color:#81a1c1">string</span> id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">repeated</span> Users users <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>The tool assumes that <code>user.id</code> is a string because it was the first type seen for that field. In this case, this is probably the wrong behavior. If you switch the order, you will get a uint32 as the type:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;users&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">{</span><span style="color:#81a1c1">&#34;type&#34;</span><span style="color:#eceff4">:</span><span style="color:#a3be8c">&#34;user&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">&#34;id&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1234</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">{</span><span style="color:#81a1c1">&#34;type&#34;</span><span style="color:#eceff4">:</span><span style="color:#a3be8c">&#34;admin&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">&#34;id&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;admin-1&#34;</span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Yields:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">SomeMessage</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">Users</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>        <span style="color:#81a1c1">string</span> type <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>        <span style="color:#81a1c1">uint32</span> id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">repeated</span> Users users <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>This is absolutely wrong, because there&rsquo;s no reasonable way to encode &ldquo;admin-1&rdquo; as a single <code>uint32</code> value. This behavior can also happen in a list that contains multiple types:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;items&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;2&#34;</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">3.3</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">null</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>results in:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">SomeMessage</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">repeated</span> <span style="color:#81a1c1">uint32</span> items <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>This tool could definitely be smarter about this. In this particular case, I might have used the <a href="https://protobuf.dev/reference/protobuf/google.protobuf/#value" rel="external">google.protobuf.Value</a> well-known type to indicate that the array can have nulls, numbers, strings, booleans, or a list/struct that contains other <code>google.protobuf.Value</code> values. This type is very useful for working with dynamically typed fields and maps very cleanly to JSON.</p>
<p>You should note that having a single field that can have multiple types is frowned upon and is considered bad API design. If you have these cases, you may want to use this conversion process to move to a &ldquo;one field: one type&rdquo; design and not carry forward this bad API design into protobuf.</p>
<h2 id="the-missing-warning-label">The missing warning label</h2>
<p>So from this exploration, we can come up with some guidelines and warnings for using JSON-to-Proto:</p>
<ul>
<li><strong>Provide comprehensive and well-structured JSON examples</strong>: The converter relies on the provided JSON data to infer the protobuf schema. Make sure your examples are complete, representing all possible field types and variations within your data structure. Well-structured JSON with clear nesting and consistent naming conventions will further improve the accuracy of the conversion.</li>
<li><strong>Manually review and refine the generated protobuf schema</strong>: The converter is not infallible. It&rsquo;s crucial to carefully examine the generated protobuf schema, ensuring that it accurately reflects your data requirements and adheres to best practices. Consider aspects like field naming, data types, and the use of enums or nested messages to optimize the schema for performance and maintainability.</li>
<li><strong>Use the converter as a starting point, not a definitive solution</strong>:  The converter can quickly generate a protobuf schema, but it&rsquo;s unlikely to be perfect right away. Treat the output as a draft and iterate on it based on your specific needs and protobuf best practices.</li>
<li><strong>Avoid ambiguous or inconsistent JSON data</strong>: JSON&rsquo;s flexibility can lead to ambiguity, which can confuse the converter. Try to maintain consistent data types within fields and avoid using null values whenever possible.</li>
<li><strong>Consider edge cases</strong>: Ensure your JSON examples cover a wide range of possible scenarios, including edge cases and potential variations in your data. This will help the converter generate a more robust and adaptable protobuf schema.</li>
<li><strong>Use protobuf&rsquo;s other features</strong>: Once you&rsquo;ve generated an initial schema, explore how protobuf&rsquo;s advanced features, such as enums, oneof fields, and maps, can be used to further refine and optimize your data representation.</li>
<li><strong>It may be better to avoid this tool altogether</strong>: Because there are so many caveats, you may want to stick to writing the protobuf schemas yourself, and avoiding this tool altogether.</li>
</ul>
<p>By adhering to these best practices, you can leverage the JSON-to-Proto converter effectively while minimizing potential issues and ensuring the resulting protobuf schema aligns with your requirements and best practices.</p>
<h2 id="not-a-replacement-for-understanding-protobuf">Not a replacement for understanding protobuf</h2>
<p>JSON to protobuf converters <em>can</em> be helpful in specific scenarios, especially for quick experimentation and legacy system integration. However, they should not be seen as a substitute for gaining a solid understanding of protobuf&rsquo;s concepts and syntax. By investing time in <a href="https://protobuf.dev/" rel="external">learning protobuf</a> directly, developers can leverage its full potential, designing schemas that are not only more expressive but also optimized for performance and maintainability. Remember, converters like this are tools, not magic wands. Use them wisely, but never let it replace the power of knowledge and try to recognize when this tool might not actually help you.</p>
<h2 id="roast-me">Roast Me</h2>
<p>I get a fair amount of criticism with my articles sometimes, but I&rsquo;ve always seen much harsher critiques from myself and from AI. I do, in fact, use AI to help with my writing process. However, rarely does AI-text directly make it into a final draft. Instead, I have iterations where I ask the AI to point out mistakes, and try to be my devil&rsquo;s advocate. Sometimes it brings up good points. Sometimes it&rsquo;s absolutely crazy. I feel like this has slowly improved my writing style and pointed out super obvious places where I need to transition better. However, sometimes I just ask it to &ldquo;roast me&rdquo; to prep me for responses I may receive. So here&rsquo;s Google Gemini&rsquo;s response to this article. I felt like this is worth adding here, because some of it needs to be said.</p>
<blockquote>
<p>Wow, this article is a masterclass in taking a shortcut and hoping for the best. You know, Protobuf exists for a reason – it&rsquo;s efficient, it&rsquo;s fast, and it forces you to actually think about your data structures. But hey, who needs that when you can just mash a button and get a half-baked schema that might kinda-sorta work?</p>
</blockquote>
<blockquote>
<p>Let&rsquo;s be real: this JSON-to-Proto tool is the equivalent of using Google Translate for your high school French essay. Sure, it might get you a passing grade, but you&rsquo;re not fooling anyone. Your schema will be riddled with Any types, missed opportunities for enums, and a general sense of &ldquo;I have no idea what I&rsquo;m doing.&rdquo;</p>
</blockquote>
<blockquote>
<p>The article itself tries to sound all responsible, warning you about the limitations of the tool. But let&rsquo;s be honest, it&rsquo;s basically saying, &ldquo;Go ahead, use this thing, but don&rsquo;t blame us when your production system melts down because you couldn&rsquo;t be bothered to learn the difference between a uint32 and a string.&rdquo;</p>
</blockquote>
<blockquote>
<p>Look, if you&rsquo;re serious about using Protobuf, do yourself a favor and actually learn it. It&rsquo;s not that hard. And if you&rsquo;re not serious, well, then just stick to JSON and accept the performance hit. Either way, stop trying to find the easy way out. It&rsquo;s called software engineering for a reason.</p>
</blockquote>
]]></content:encoded></item><item><title>gRPC: The Ugly Parts</title><link>https://kmcd.dev/posts/grpc-the-ugly-parts/</link><pubDate>Tue, 03 Sep 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/grpc-the-ugly-parts/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/grpc-the-ugly-parts/cover_hu_3d7170590a3ed024.webp" /> &lt;/p>
                
                The seedy underbelly of gRPC.
                </description><content:encoded><![CDATA[<p>gRPC has undeniably become a powerful tool in the world of microservices, offering efficiency and performance benefits, but gRPC also has an ugly side. As someone who&rsquo;s spent a considerable amount of time with gRPC, I&rsquo;d like to shed light on some of the uglier aspects of this technology. I&rsquo;ve already talked about the <a href="https://kmcd.dev/posts/grpc-the-good-parts">good</a> and <a href="https://kmcd.dev/posts/grpc-the-bad-parts">bad</a> parts of gRPC, now let&rsquo;s talk about the ugly.</p>
<h2 id="generated-code">Generated Code</h2>
<p>To get started, I have to talk about how ugly the code generated from protobuf definitions is. It has historically been verbose, complex, and difficult to navigate. Even though it&rsquo;s not meant to be hand-edited, this can impact code readability and maintainability, especially when integrating gRPC into larger projects. This has actually improved a lot recently in most languages but even so, there are some rough edges.</p>
<h3 id="language-specific-quirks">Language-specific Quirks</h3>
<p>Protobuf and gRPC&rsquo;s initial implementations often diverged from language-specific norms, especially in their HTTP handling. This stemmed partly from the decision to mandate HTTP/2 support, a decision that has since proven to limit gRPC&rsquo;s reach into the web frontend. We know now from gRPC-Web that trailers aren&rsquo;t a hard requirement for a protocol like gRPC. In the aftermath of this decision, we are now left with a need to evolve the language implementations of protobuf and gRPC to be more idiomatic for each language.</p>
<p>For Go, avoiding the <code>net/http</code> package is a rough decision because it makes it harder to use gRPC endpoints alongside other kinds of HTTP APIs and to re-use HTTP middleware. They eventually added a <a href="https://pkg.go.dev/google.golang.org/grpc#Server.ServeHTTP" rel="external"><code>ServeHTTP()</code></a> interface to grpc-go as an experimental way to use the HTTP server from the Go standard library but using that method results in <a href="https://kmcd.dev/posts/benchmarking-go-grpc/">a significant loss of performance</a>. Maybe they did it for performance reasons? If so, it&rsquo;s definitely a tradeoff that has split gRPC from the rest of the Go ecosystem.</p>
<p>Sometimes language quirks actually impact how you design protobuf types. If you follow the style recommendations from <a href="https://buf.build/docs/best-practices/style-guide" rel="external">Buf</a>, the names of enums are expected to be prefixed an upper-snake-case version of the enum name, like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">enum</span> FooBar <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  FOO_BAR_UNSPECIFIED <span style="color:#81a1c1">=</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  FOO_BAR_FIRST_VALUE <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  FOO_BAR_SECOND_VALUE <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>This is described better in the <a href="https://buf.build/docs/lint/rules#enum_value_prefix" rel="external">buf lint rule description</a> for <code>ENUM_VALUE_PREFIX</code> but the style guide is like this because of C++ scoping rules with enums, which makes it impossible to have two enum values in the same package with the same enum value name. While this convention originated from C++ scoping rules, it affects how you should design all protobuf files. Why would scoping inside of the enum not be enough for the C++ compiler to generate unique names? Why is this flaw something that impacts the style guide and, in effect, all target languages? To me, this is kind of ugly, because quirks of some language implementations are bubbling up in unintuitive ways.</p>
<h3 id="the-generated-code-isnt-even-that-fast">The generated code isn&rsquo;t even that fast</h3>
<p>One benefit of generated code is that you can generate code that no sane human would write in order to get some performance optimizations. However, if you look at some of the code generated from protobuf you&rsquo;ll see runtime reflection used a lot. Why? In a way, I am saying the generated code <em>isn&rsquo;t ugly enough</em>. Let&rsquo;s look at a concrete example. Be warned that this will be a very Go-specific section because most of my experience with protobufs is in Go. However, the same strategy has been applied in most languages.</p>
<p>Let&rsquo;s take a look at super a simple example in Go. Here&rsquo;s the protobuf:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">Hello</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>Here&rsquo;s the type generated by protoc:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> Hello <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	state         protoimpl<span style="color:#eceff4">.</span>MessageState
</span></span><span style="display:flex;"><span>	sizeCache     protoimpl<span style="color:#eceff4">.</span>SizeCache
</span></span><span style="display:flex;"><span>	unknownFields protoimpl<span style="color:#eceff4">.</span>UnknownFields
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	Name <span style="color:#81a1c1">string</span> <span style="color:#a3be8c">`protobuf:&#34;bytes,1,opt,name=name,proto3&#34; json:&#34;name,omitempty&#34;`</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// With these methods, contents are stripped</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">*</span>Hello<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Reset</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">*</span>Hello<span style="color:#eceff4">)</span> <span style="color:#88c0d0">String</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">*</span>Hello<span style="color:#eceff4">)</span> <span style="color:#88c0d0">ProtoMessage</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">*</span>Hello<span style="color:#eceff4">)</span> <span style="color:#88c0d0">ProtoReflect</span><span style="color:#eceff4">()</span> protoreflect<span style="color:#eceff4">.</span>Message
</span></span></code></pre></div><p>There&rsquo;s actually no <code>Marshal()</code> or <code>Unmarshal()</code> functions defined specifically for this type. This means that runtime reflection is used to make serialization work. Reflection is generally seen as slower, because it <em>is</em> slower. I find it strange that optimized, type-specific serialization code isn&rsquo;t being generated for Go. That said, you can actually get this by using a separate protoc plugin called <a href="https://github.com/planetscale/vtprotobuf" rel="external">vtprotobuf</a> that will generate specialized marshal and unmarshal functions for each protobuf type. It also allows for using type-specific memory pools, which can also help reduce allocations and improve performance. From my <a href="https://kmcd.dev/posts/benchmarking-go-grpc/">own testing</a> just adding <code>vtprotobuf</code> with zero code changes can improve performance by 2-4%. This is essentially a &ldquo;free&rdquo; 2-4%, so it&rsquo;s super strange to me that this wouldn&rsquo;t be part of the standard compiler. <a href="https://github.com/sudorandom/go-grpc-bench/blob/v0.0.1/gen/flex_vtproto.pb.go#L573" rel="external">You may not like it, but this is what peak performance looks like</a>. Anyway, this project needs more love and support.</p>
<p>Note that there are <a href="https://medium.com/@octopus.dev/gremlin-77af6fee4193" rel="external">other efforts which claim outrageous improvements</a> over what the standard protobuf library does. They do make tradeoffs to achieve these performance gains, but many times the extra complexity is worth it.</p>
<p>You might have read this section and thought &ldquo;well, this would increase the amount of code being generated and increase binary or package sizes and in some environments, you might not want that. That&rsquo;s true amd that&rsquo;s why protobuf has an <code>optimize_for</code> option, so you can annotate one of the following:</p>
<ul>
<li><code>option optimize_for = SPEED;</code> - more verbose, faster code</li>
<li><code>option optimize_for = CODE_SIZE;</code> - smaller code</li>
<li><code>option optimize_for = LITE_RUNTIME;</code> - intended to run on a smaller runtime that omits features like descriptors and reflection.</li>
</ul>
<p>See the full description for optimize_for on the <a href="https://protobuf.dev/programming-guides/proto3/" rel="external">official protobuf documentation</a>. While these options exist, they aren&rsquo;t actually used for most target languages. In the future I would totally like to see most of <code>vtprotobuf</code> be rolled into the standard protobuf compiler for Go and be used if <code>optimize_for = SPEED</code>. Integrating <code>vtprotobuf</code>-like optimizations into the standard protobuf compiler could offer significant performance gains for Go and there are potentially similar opportunities in other languages as well.</p>
<h2 id="required-fields">Required Fields</h2>
<p>The maintainers of protobuf learned some hard lessons with required fields. They felt like they misstepped so badly, that they made a new version of protobuf, proto3, just to remove required fields from the spec. Why? The author of the &ldquo;Required considered harmful&rdquo; manifesto talks about this in a <a href="https://news.ycombinator.com/item?id=18190005" rel="external">lengthy hacker news comment</a>, but the important bit is:</p>
<blockquote>
<p>Real-world practice has also shown that quite often, fields that originally seemed to be &ldquo;required&rdquo; turn out to be optional over time, hence the &ldquo;required considered harmful&rdquo; manifesto. In practice, you want to declare all fields optional to give yourself maximum flexibility for change.</p>
</blockquote>
<p>This is <a href="https://protobuf.dev/programming-guides/dos-donts/#add-required" rel="external">echoed by the official style guide of protobufs</a>, where they recommend adding a comment indicating that a field is required. If we&rsquo;re talking about getting a message from A to B, I totally agree with this line of thinking. However, just because the fields that are considered &ldquo;required&rdquo; change over time doesn&rsquo;t mean required fields don&rsquo;t exist. There still needs to be code that enforces this requirement and I&rsquo;d rather not write this code, to be honest. Therefore, I think the best way of handling required fields without writing a bunch of null checks everywhere is by using <a href="https://github.com/bufbuild/protovalidate" rel="external">protovalidate</a> or a similar library that has protobuf options that allow you to annotate which fields are required. Then there is code on the server and/or client that can enforce these requirements using a library. In my opinion, this has the best of both worlds: you can still declare required fields in a way that doesn&rsquo;t completely break message integrity.</p>
<p>I don&rsquo;t like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">User</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">int32</span> age <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span> <span style="color:#616e87;font-style:italic">// required.
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>I do like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">User</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">int32</span> age <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span> <span style="color:#eceff4">[(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1;font-weight:bold">required</span> <span style="color:#81a1c1">=</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>I&rsquo;m a big fan of <a href="https://github.com/bufbuild/protovalidate" rel="external">protovalidate</a> and I&rsquo;ve used it a good amount and have contributed to it. Generally, I think <a href="https://protobuf.dev/programming-guides/proto3/#customoptions" rel="external">custom options</a> for protobuf fields is an untapped superpower of protobufs.</p>
<h2 id="failure-to-launch">Failure to Launch</h2>
<p>While gRPC has undeniable advantages, its learning curve can be steep. Getting started with protobuf, understanding the tooling, and setting up the necessary infrastructure can be intimidating for newcomers, making the initial adoption hurdle higher than with simpler JSON-based APIs. Why is it steep? Well, it introduces non-idiomatic tooling to most languages. There are some examples of language support that make protobuf generation seamless. <a href="https://learn.microsoft.com/en-us/aspnet/core/grpc/basics" rel="external">Grpc.Tools</a> for .NET is one shining example, showing how protobuf tooling can be more integrated into standard language tooling. We need more of this.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-the-ugly-parts/learning-curve_hu_c461c8a49bfd8bff.webp" class="center" width="600px"/>
    


<p>The steep learning curve doesn&rsquo;t help when many people who use and rely on protobuf and gRPC actively don&rsquo;t want gRPC to extend to the frontend and think that pushing in this direction will lead to uninformed people encroaching on the domain of the backend, where only they are smart enough to work. This is elitist gate-keeping and is unfortunately prevalent in this industry. I believe gRPC has as much of a place in web frontends as much as it does in microservices.</p>
<p>I&rsquo;ve learned a lot by helping others work with protobuf. You may see me on <a href="https://buf.build/links/slack" rel="external">Buf&rsquo;s slack channel</a> or on related discussions because I truly have gotten a lot out of it. Many article ideas have come directly from answering questions there. If I see a problem often enough, I may end up writing an article about it. I believe the protobuf and gRPC community needs more of this attitude.</p>
<p>I believe the steep learning curve (which can be helped with tooling), coupled with some resistance from backend developers (which can be helped by&hellip; having empathy?), has slowed its broader adoption in web development.</p>
<h2 id="grpc-has-a-history">gRPC Has a History</h2>
<p>gRPC&rsquo;s initial focus on microservices and its close ties to HTTP/2 hindered its widespread adoption in web development. Even with the <a href="https://grpc.io/blog/state-of-grpc-web/" rel="external">advent of gRPC-Web</a>, there&rsquo;s still a perception that it&rsquo;s not a first-class citizen in the frontend ecosystem. The lack of robust integration with popular frontend libraries like <a href="https://tanstack.com/query/latest" rel="external">TanStack Query</a> further solidifies this notion to me.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-the-ugly-parts/bad-blood_hu_692216ba0338ffdf.webp" class="center" width="800px"/>
    


<p>I think there&rsquo;s a real chance to get more frontend developers excited about gRPC with improved tooling. There&rsquo;s a giant industry-wide conversation happening right now around where the line between &ldquo;frontend&rdquo; and &ldquo;backend&rdquo; meet and I think no matter the outcome, we&rsquo;re going to see more typescript code using gRPC.</p>
<h2 id="the-g-in-grpc">The &ldquo;g&rdquo; in gRPC</h2>
<p>While <a href="https://grpc.io/docs/what-is-grpc/faq/#what-does-grpc-stand-for" rel="external">the gRPC project claims</a> that the &ldquo;g&rdquo; in gRPC is a <a href="https://en.wikipedia.org/wiki/Backronym" rel="external">backronym</a> that stands for &ldquo;gRPC&rdquo;, it originally stood for Google, because it was Google who developed and released both protobuf and gRPC.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-the-ugly-parts/google_hu_7e5494754266e479.webp" class="center" width="600px"/>
    


<p>There&rsquo;s always a lingering question about Google&rsquo;s long-term commitment to gRPC and protobuf. Will they continue to invest in these open-source projects, or could they pull the plug if priorities shift? Remember that Google has <a href="https://techcrunch.com/2024/05/01/google-lays-off-staff-from-flutter-dart-python-weeks-before-its-developer-conference/" rel="external">recently layed off much of the Flutter, Dart and Python teams</a>. The protobuf community is growing, but would it be self-sustaining enough to survive such a scenario?</p>
<h2 id="its-not-finished">It&rsquo;s Not Finished</h2>
<p>Others have said that gRPC is immature, not because of its age but by how developed the ecosystem is. I tend to agree, because it&rsquo;s missing features and tools that I would have expected from a mature ecosystem.</p>
<h3 id="the-missing-package-manager">The missing package manager</h3>
<p>Sharing protobuf definitions across multiple projects or repositories is a constant struggle without specialized tools. While solutions like <a href="https://bazel.build/reference/be/protocol-buffer" rel="external">Bazel</a>, <a href="https://www.pantsbuild.org/2.21/docs/go/integrations/protobuf" rel="external">Pants</a>, and <a href="https://buf.build/product/bsr" rel="external">Buf&rsquo;s BSR</a> exist, my experience with protobuf &ldquo;in the real world&rdquo; is&hellip; mixed. There are prominent open source projects, some by Google, that have bash scripts scrapped together to download dependencies before evoking <code>protoc</code> manually. Just imagine a programming language with no solution for managing dependencies. That&rsquo;s insane. I think both <a href="https://grpc.io/blog/bazel-rules-protobuf/" rel="external">Bazel</a> and <a href="https://buf.build/docs/ecosystem/cli-overview" rel="external">Buf tooling</a> solve this problem pretty well but I&rsquo;m just frustrated that every repo I come across that uses protobuf solves the problem in the most bespoke way possible. The community needs to come together to improve this. There is an open-source repo called <a href="https://github.com/helsing-ai/buffrs" rel="external">Buffrs</a> that appears to be tackling this problem. I haven&rsquo;t used it personally but it looks decent so far.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-the-ugly-parts/build_hu_48d6d71ba4fbbe87.webp" class="center" width="600px"/>
    


<p>Related to dependencies, I do want to call out that <a href="https://protobuf.dev/reference/protobuf/google.protobuf/" rel="external">Google&rsquo;s &ldquo;well-known&rdquo; protobuf types</a> get special privilege of being built into protoc. While these types are incredibly useful and invaluable, their privilege makes it hard for other libraries of useful protobuf types to exist and thrive. Just building these protobuf definitions into protoc (and other tooling) is a cop out for not having a real and consistent story for dependency management.</p>
<h3 id="editor-support">Editor Support</h3>
<p>Editor integration for protobuf code generation leaves a lot to be desired. It would be immensely helpful if editors could intelligently link generated code back to its protobuf source. This would provide a more seamless experience, but the tooling just isn&rsquo;t smart enough yet. Also, I think everyone needs to run with <a href="https://buf.build/docs/editor-integration" rel="external">Buf&rsquo;s editor support</a>. Having a linter and autoformatter built into your editor is the expected from developers nowadays. And with protobuf, there are <a href="https://buf.build/docs/lint/rules" rel="external">extremely real reasons</a> to follow the advice of the linter.</p>
<p>Projects like <a href="https://trpc.io/" rel="external">tRPC</a> showcase the benefits of tight integration and opinionated design choices—something that protobuf, by its nature, can&rsquo;t fully replicate. However, I remain hopeful that the protobuf ecosystem can evolve to offer a similarly streamlined developer experience.</p>
<h3 id="ugly-documentation">Ugly Documentation</h3>
<p>I&rsquo;ve never seen documentation generated from protobuf that wasn&rsquo;t super ugly. I think since gRPC has historically been a backend service, the backend devs never bothered to put any real effort into making pretty documentation output using a protoc plugin. I&rsquo;ve solved this problem by <a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">making a protoc plugin</a> that generates OpenAPI from given protobuf files. Then I use one of the many beautiful tools for displaying the OpenAPI spec. This was, by far, much easier than getting me to make a decent design. Another side benefit for generating OpenAPI from protobuf is the ability to tap into that ecosystem since there&rsquo;s more to it than just documentation.</p>
<p>Let&rsquo;s look at a real example. This is a document generated using one of the few tools for generating documentation from protobuf, <a href="https://github.com/pseudomuto/protoc-gen-doc" rel="external">protoc-gen-doc</a>:</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-the-ugly-parts/protoc-gen-doc_hu_64610573235f8641.webp" class="center" width="600px"/>
    


<p>Compare it to some of the OpenAPI tooling. This was generated using <a href="https://github.com/stoplightio/elements" rel="external">Elements</a>, but there are many, many other alternatives that look equally as polished:</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-the-ugly-parts/elements_hu_e3b5d245aba88abe.webp" class="center" width="800px"/>
    


<p>It&rsquo;s kind-of not fair to point at a single plugin and say that the default template doesn&rsquo;t look as good as OpenAPI alternatives, because you actually do have more flexibility with protoc-gen-doc. It allows you to specify your own template so it could look as beautiful as you want. However, this does line up with my point: the tooling is more finished and polished in the REST world than gRPC. This is a fixable problem, but we need to get frontend devs and designers excited about gRPC or backend engineers need to start sharpening their design skills.</p>
<p>I also want to note that OpenAPI/Swagger interfaces often have a way to test endpoints directly from the documentation website. This is completely missing from equivalent tools in the gRPC world. Additionally, with most OpenAPI documentation tools you can clearly see which fields are required and will display constraints on fields that have them. So not only is it prettier, it&rsquo;s more functional as well.</p>
<h2 id="conclusion">Conclusion</h2>
<p>gRPC, while a powerful tool in many ways, still has room to grow.  The less-than-ideal aspects of generated code, coupled with the challenges of dependency management and evolving protobuf schemas, can create friction for developers. The lack of intuitive editor integration and the historical focus on backend services have also hindered its wider adoption in web development.</p>
<p>However, I think the future of gRPC is bright and can be far less ugly. The community is actively addressing these challenges, developing tools like <a href="https://buf.build/product/cli" rel="external">the buf CLI</a>, <a href="https://github.com/bufbuild/protovalidate" rel="external">protovalidate</a> and <a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">protoc-gen-connect-openapi</a> to bridge the gaps and enhance the developer experience. As gRPC matures and <a href="https://trends.google.com/trends/explore?date=all&amp;q=%2Fm%2F04dzxdz,%2Fg%2F11cp5mklv8,RESTful&amp;hl=en" rel="external">its ecosystem expands</a>, we can anticipate improved tooling, better editor support, and a smoother integration into the frontend world.</p>
]]></content:encoded></item><item><title>Working with Protobuf in 2024</title><link>https://kmcd.dev/posts/working-with-protobuf-in-2024/</link><pubDate>Tue, 27 Aug 2024 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/working-with-protobuf-in-2024/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/working-with-protobuf-in-2024/cover_hu_c4edfa33fe867816.webp" /> &lt;/p>
                
                Tools and tricks for developing with protobuf.
                </description><content:encoded><![CDATA[<p>Protocol Buffers (protobuf), Google&rsquo;s brainchild for efficient data serialization, have become an indispensable tool in the modern software development landscape. They offer a compact and efficient way to structure data for storage or transmission, making them ideal for applications like gRPC services, data storage, and inter-service communication. gRPC is even branching into the frontend of the web, with gRPC-Web and ConnectRPC. This means that the traditional protobuf workflow can sometimes feel a bit dated and cumbersome with tooling that isn&rsquo;t quite made to be easy to use and, worse, can be easy to use incorrectly. In this article, we&rsquo;ll explore some modern tools that address these pain points, making protobuf development more enjoyable and productive. But first, let&rsquo;s recap how the &ldquo;traditional&rdquo; protobuf workflow typically works.</p>
<h2 id="traditional-workflow">Traditional Workflow</h2>
<p>Here&rsquo;s what a typical workflow can look like when working with protobufs:</p>
<ol>
<li><strong>Define the Protobuf File:</strong>  You create a <code>.proto</code> file, defining your message structures, enums, and services (if using gRPC).</li>
<li><strong>Compile with <code>protoc</code>:</strong> You use the <code>protoc</code> compiler to generate code in your desired languages (e.g., Go, Java, Python, Rust, etc.) from your <code>.proto</code> files.</li>
<li><strong>Implement and Use:</strong> You implement the server-side logic (if applicable) using the generated server stubs and utilize the generated client code to interact with your protobuf-based services.</li>
</ol>
<p>Let&rsquo;s visualize this traditional workflow to highlight these pain points:</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">

    
    
        
        
        <img src="https://kmcd.dev/posts/working-with-protobuf-in-2024/workflow-legacy.svg" class="center" height="600px"/>
    



    </span>

    
</div>

<h3 id="whats-missing">What&rsquo;s Missing?</h3>
<p>While this workflow gets the job done, it leaves a lot of room for improvement:</p>
<ul>
<li>Error-Prone Build Steps: The process often involves manual compilation and code generation, increasing the risk of human errors. Most people introduce a <code>Makefile</code> or a bash script to handle calling <code>protoc</code>, but even this can be error-prone and difficult to maintain. Some believe this doesn&rsquo;t exist or some magical build system &ldquo;handles it for you&rdquo;, but I assure you, <a href="https://github.com/openconfig/gnmi/blob/master/compile_protos.sh" rel="external"> it exists</a> and it&rsquo;s <a href="https://github.com/search?q=protoc&#43;language%3Abash&amp;type=code" rel="external">not hard to find</a> plenty of examples of this.</li>
<li>This workflow doesn&rsquo;t include a standardized way to handle protobuf dependencies. Traditionally, this involves the Makefile or bash script that I just mentioned to download protobufs from various online repositories or manually copying them between projects. This is bad, but the Protobuf/gRPC projects don&rsquo;t really give much guidance on how to reuse protobufs, so people have just done it themselves in wildly different ways.</li>
<li>Lack of Consistency: Without proper tooling, it&rsquo;s challenging to maintain consistent formatting and styling across multiple <code>.proto</code> files.</li>
<li>Breaking Changes: Manually tracking changes to your protobufs and ensuring backward compatibility can be tedious and error-prone.</li>
<li>No Mocking or Prototyping: It takes a good amount of effort to make a gRPC service, even if you just want to use mock data.</li>
</ul>
<p>Fortunately, the Protobuf ecosystem has evolved dramatically in the last few years, and most of the advancement is from third-parties (not Google), which is exciting. So let&rsquo;s cover parts of the modern workflow that you won&rsquo;t see in a gRPC or protobuf tutorial.</p>
<h2 id="the-next-generation-of-protobuf-tooling">The next generation of protobuf tooling</h2>
<p>The Protobuf ecosystem has seen a surge of innovation in recent years, with many third-party tools emerging to address the limitations of the traditional workflow. Let&rsquo;s delve into some of these tools.</p>
<h3 id="json-to-proto">JSON to Proto</h3>
<p><a href="https://json-to-proto.github.io/" rel="external">JSON to Proto</a> is a online tool that simplifies the creation of protobuf definitions if you are brand new to protobufs. I wouldn&rsquo;t recommend using this for the long haul, but it can be a good way to get started quickly by pasting in sample JSON data, and the tool will generate a corresponding <code>.proto</code> file for you. This is particularly useful when you&rsquo;re starting with existing JSON data or want to quickly prototype a protobuf schema. Learn more from <a href="https://github.com/json-to-proto/json-to-proto.github.io" rel="external">the Github repo</a>.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/working-with-protobuf-in-2024/json-to-proto_hu_5cb681304f648615.webp" class="center" width="600px"/>
    


<h3 id="protobuf-pal">Protobuf Pal</h3>
<p><a href="https://www.protobufpal.com/" rel="external">Protobuf Pal</a> is a browser-based protobuf editor designed to streamline the creation and editing of <code>.proto</code> files. It offers features like syntax highlighting, error checking, and auto-completion, making it easier to write valid protobuf definitions.</p>
<h3 id="buf-cli">Buf CLI</h3>
<p><a href="https://buf.build/" rel="external">Buf</a> is a comprehensive toolkit designed to make working with protobufs a breeze.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/working-with-protobuf-in-2024/surprise_hu_8964afe9cde880a4.webp" class="center" width="500px"/>
    


<p>The Buf CLI offers several powerful features:</p>
<ul>
<li><strong><code>buf generate</code>:</strong>  A replacement for <code>protoc</code> that provides consistent code generation across different languages and environments. <a href="https://buf.build/docs/generate/tutorial" rel="external">Read more here.</a></li>
<li><strong><code>buf lint</code>:</strong>  A linter that helps you maintain clean and consistent protobuf definitions, catching potential issues early. <a href="https://buf.build/docs/lint/tutorial" rel="external">Read more here.</a></li>
<li><strong><code>buf format</code>:</strong> An opinionated formatter to ensure consistent styling across your <code>.proto</code> files. <a href="https://buf.build/blog/introducing-buf-format" rel="external">Read more here.</a></li>
<li><strong><code>buf curl</code>:</strong> is useful not only for development but also for testing. You can use it to send gRPC requests from your terminal, making it easy to verify the behavior of your services. It can craft requests using server reflection, local protobuf files, descriptor files or from a reference to the buf.build registry. <a href="https://buf.build/docs/curl/usage" rel="external">Read more here.</a></li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl --list-methods https://demo.connectrpc.com
</span></span><span style="display:flex;"><span>connectrpc.eliza.v1.ElizaService/Converse
</span></span><span style="display:flex;"><span>connectrpc.eliza.v1.ElizaService/Introduce
</span></span><span style="display:flex;"><span>connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span>$ buf curl -d <span style="color:#a3be8c">&#39;{&#34;sentence&#34;:&#34;Hello! I need some help, doc&#34;}&#39;</span> https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;sentence&#34;</span>: <span style="color:#a3be8c">&#34;Hello...I&#39;m glad you could drop by today.&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>You can use the buf CLI directly on the CLI (which is great when you&rsquo;re playing around or adding it to a continuous integration process) or <a href="https://buf.build/docs/editor-integration" rel="external">integrated into your code editor</a>, which is where the real magic happens, anyway.</p>
<h3 id="buf-schema-registry-bsr">Buf Schema Registry (BSR)</h3>
<p><a href="https://buf.build/docs/bsr/introduction" rel="external">The Buf Schema Registry (BSR)</a> is the missing package manager for Protobufs, but it does a bit more than that. Not only does it allow you to push versioned schemas to one place, but it also has cool features like <a href="https://buf.build/docs/bsr/generated-sdks/overview" rel="external">automatically generated SDKs</a>. The idea is that you can just import the package in your favorite language and magically you have your server stubs and clients in the language of your choice (as long as it&rsquo;s Go, Typescript/Javascript, Java/Kotlin, Swift, Python or Rust).</p>
<p>With the BSR, you no longer need bespoke Makefile or bash scripts to pull down protobuf dependencies.</p>
<h2 id="testing">Testing</h2>
<p>This next set of tools are good for testing live gRPC endpoints. Obviously, <a href="https://github.com/fullstorydev/grpcurl" rel="external">gRPCurl</a> is an amazing tool for this, but let&rsquo;s discover some tools that are a bit newer to the scene.</p>
<h3 id="buf-studio">Buf Studio</h3>
<p><a href="https://buf.build/studio" rel="external">Buf Studio</a> is an interactive web UI for all your gRPC and Protobuf services stored on the Buf Schema Registry. With Buf Studio you can craft gRPC/gRPC-Web/Connect requests using images on the buf registry. Buf Studio uses those protobuf schemas to support autocompletion of these requests, which is super cool. It can also use an agent that is built into the <code>Buf CLI</code> to proxy requests from internal networks, making this web-based tool a bit more flexible.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/working-with-protobuf-in-2024/buf-studio_hu_f94709d7fdb4af06.webp" class="center" width="600px"/>
    


<h3 id="postman">Postman</h3>
<p><a href="https://blog.postman.com/postman-now-supports-grpc/" rel="external">Postman</a>, a popular API testing tool, now supports gRPC. You can leverage its familiar interface to construct and send gRPC requests, making it a convenient option for testing your protobuf services.</p>
<h3 id="insomnia">Insomnia</h3>
<p><a href="https://docs.insomnia.rest/insomnia/grpc" rel="external">Insomnia</a> is another API testing platform that has added gRPC support. Similar to Postman, it allows you to design and execute gRPC requests within its user-friendly environment.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/working-with-protobuf-in-2024/insomnia_hu_de0279688fd9a3cf.webp" class="center" width="800px"/>
    


<p>Insomnia&rsquo;s UI takes a little getting used to but once gRPC is set up, it gets easier.</p>
<h3 id="k6">k6</h3>
<p><a href="https://grafana.com/docs/k6/latest/using-k6/protocols/grpc/" rel="external">k6</a> is a powerful load testing tool that can be used to simulate heavy traffic on your gRPC services. It helps you identify performance bottlenecks and ensure your services can handle real-world loads.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">{</span> Client<span style="color:#eceff4">,</span> StatusOK <span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">from</span> <span style="color:#a3be8c">&#39;k6/net/grpc&#39;</span><span style="color:#eceff4">;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">{</span> check<span style="color:#eceff4">,</span> sleep <span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">from</span> <span style="color:#a3be8c">&#39;k6&#39;</span><span style="color:#eceff4">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">const</span> client <span style="color:#81a1c1">=</span> <span style="color:#81a1c1;font-weight:bold">new</span> Client<span style="color:#eceff4">();</span>
</span></span><span style="display:flex;"><span>client<span style="color:#eceff4">.</span>load<span style="color:#eceff4">([</span><span style="color:#a3be8c">&#39;definitions&#39;</span><span style="color:#eceff4">],</span> <span style="color:#a3be8c">&#39;eliza.proto&#39;</span><span style="color:#eceff4">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">export</span> <span style="color:#81a1c1;font-weight:bold">default</span> <span style="color:#eceff4">()</span> <span style="color:#81a1c1">=&gt;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  client<span style="color:#eceff4">.</span>connect<span style="color:#eceff4">(</span><span style="color:#a3be8c">&#39;127.0.0.1:10000&#39;</span><span style="color:#eceff4">,</span> <span style="color:#eceff4">{});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1;font-weight:bold">const</span> data <span style="color:#81a1c1">=</span> <span style="color:#eceff4">{</span> sentence<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#39;Hello, doc!&#39;</span> <span style="color:#eceff4">};</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1;font-weight:bold">const</span> response <span style="color:#81a1c1">=</span> client<span style="color:#eceff4">.</span>invoke<span style="color:#eceff4">(</span><span style="color:#a3be8c">&#39;connectrpc.eliza.v1.ElizaService/Say&#39;</span><span style="color:#eceff4">,</span> data<span style="color:#eceff4">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  check<span style="color:#eceff4">(</span>response<span style="color:#eceff4">,</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#39;status is OK&#39;</span><span style="color:#81a1c1">:</span> <span style="color:#eceff4">(</span>r<span style="color:#eceff4">)</span> <span style="color:#81a1c1">=&gt;</span> r <span style="color:#81a1c1">&amp;&amp;</span> r<span style="color:#eceff4">.</span>status <span style="color:#81a1c1">===</span> StatusOK<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  console<span style="color:#eceff4">.</span>log<span style="color:#eceff4">(</span>JSON<span style="color:#eceff4">.</span>stringify<span style="color:#eceff4">(</span>response<span style="color:#eceff4">.</span>message<span style="color:#eceff4">));</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  client<span style="color:#eceff4">.</span>close<span style="color:#eceff4">();</span>
</span></span><span style="display:flex;"><span>  sleep<span style="color:#eceff4">(</span><span style="color:#b48ead">1</span><span style="color:#eceff4">);</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">};</span>
</span></span></code></pre></div><h3 id="fauxrpc">FauxRPC</h3>
<p>I couldn&rsquo;t write this article without promoting my own tool, <a href="https://fauxrpc.com" rel="external">FauxRPC</a>. FauxRPC is a tool that enables developers to quickly generate mock gRPC servers from protobuf definitions, facilitating early API development and testing. By incorporating FauxRPC into your workflow, you can easily create realistic mock services that simulate real gRPC server behavior, allowing you to test your client implementations and identify potential issues without the need for a fully implemented backend. This streamlined prototyping and testing process ultimately fosters faster iteration and more robust API development.</p>
<p>With a single command, you can have a server running with fake data!</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf build buf.build/connectrpc/eliza -o eliza.binpb
</span></span><span style="display:flex;"><span>$ fauxrpc run --schema<span style="color:#81a1c1">=</span>eliza.binpb
</span></span><span style="display:flex;"><span>FauxRPC <span style="color:#81a1c1">(</span>0.0.16 <span style="color:#81a1c1">(</span>97e4c8caf9d3c22387a393180e00bce40b2834c6<span style="color:#81a1c1">)</span> @ 2024-08-22T18:42:56Z<span style="color:#eceff4">;</span> go1.22.4<span style="color:#81a1c1">)</span>
</span></span><span style="display:flex;"><span>Listening on http://127.0.0.1:6660
</span></span><span style="display:flex;"><span>OpenAPI documentation: http://127.0.0.1:6660/fauxrpc.openapi.html
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Example Commands:
</span></span><span style="display:flex;"><span>$ buf curl --http2-prior-knowledge http://127.0.0.1:6660 --list-methods
</span></span><span style="display:flex;"><span>$ buf curl --http2-prior-knowledge http://127.0.0.1:6660/<span style="color:#81a1c1">[</span>METHOD_NAME<span style="color:#81a1c1">]</span>
</span></span></code></pre></div><p>Learn more about it in <a href="https://kmcd.dev/posts/fauxrpc/">my previous post announcing it</a> or <a href="https://fauxrpc.com/docs/intro/" rel="external">the documentation website</a>.</p>
<h2 id="new-workflow">New Workflow</h2>
<p>This enhanced workflow empowers developers to iterate faster, catch errors earlier, and ensure API stability, ultimately leading to more robust and maintainable protobuf-based applications. Let&rsquo;s explore how these tools fit into the enhanced workflow:</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">

    
    
        
        
        <img src="https://kmcd.dev/posts/working-with-protobuf-in-2024/workflow-new.svg" class="center" width="900px"/>
    



    </span>

    
</div>

<p>Note that this workflow has one more &ldquo;find issues and iterate&rdquo; connections right after writing the protobuf file. That&rsquo;s because these new tools will help you find issues with your protobuf schemas earlier in a more automated way.</p>
<p>Also note that with FauxRPC, frontend developers (or whatever is using the generated clients for the service) can <strong>start working on their part before the backend developer is finished with their work</strong>. Frontend devs no longer have to come up with mock APIs (which rapidly get outdated with reality) just to get started on their frontend work. Integration work on another service which uses this protobuf can happen before the backend implementation is completed. Everyone can work in parallel and the better the schema is (with <a href="https://github.com/bufbuild/protovalidate" rel="external">protovalidate</a> constraints) the better the fake data will be.</p>
<h3 id="more-ways-to-start-making-protobuf-files">More ways to start making protobuf files</h3>
<p>Developers can still define services, messages, and enums directly in the .proto file. However, tools like JSON-to-Proto and Protobuf Pal provide visual aids and assistance in creating and editing .proto files, reducing errors and improving productivity. I wouldn&rsquo;t recommend these tools all the time, but they are probably good for quickly getting a prototype going quickly.</p>
<h3 id="automated-code-generation-and-management">Automated Code Generation and Management</h3>
<p>Buf&rsquo;s <code>buf generate</code> command streamlines the code generation process with a set of declarative configuration files, ensuring consistency across different languages and platforms. Buf&rsquo;s dependency management capabilities eliminate the need for manual scripts to handle protobuf dependencies.</p>
<h3 id="enhanced-quality-control">Enhanced Quality Control</h3>
<ul>
<li>Buf&rsquo;s <code>buf lint</code> enforces coding standards and best practices, catching potential issues early in development.</li>
<li>Buf&rsquo;s <code>buf format</code> automatically formats your .proto files, ensuring consistency and readability.</li>
<li>Buf&rsquo;s <code>buf breaking</code> change detection helps you avoid introducing changes that could disrupt existing clients.</li>
</ul>
<p>All of these put together mean that issues in your API schema are discovered automatically, as soon as possible.</p>
<h3 id="streamlined-testing-and-prototyping">Streamlined Testing and Prototyping</h3>
<p>Buf&rsquo;s <code>buf curl</code> and Buf Studio&rsquo;s built-in gRPC client enable quick testing and interaction with your services. FauxRPC facilitates rapid prototyping by allowing you to mock gRPC services without implementing the actual server-side logic.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The traditional protobuf workflow, while functional, can involve manual steps and potential pitfalls. By incorporating tools like the <a href="https://buf.build/product/cli" rel="external">Buf CLI</a>, <a href="https://github.com/json-to-proto/json-to-proto.github.io" rel="external">JSON-to-Proto</a>, <a href="https://www.protobufpal.com/" rel="external">Protobuf Pal</a>, and <a href="https://fauxrpc.com" rel="external">FauxRPC</a> into your development process, you can significantly enhance your productivity and ensure the quality of your protobuf definitions. Note that I haven&rsquo;t come close to outlining all of the different tools that you can use with Protobuf. So, don&rsquo;t hesitate to explore these tools and discover how they can transform your Protobuf development experience!</p>
]]></content:encoded></item><item><title>Introducing FauxRPC</title><link>https://kmcd.dev/posts/fauxrpc/</link><pubDate>Tue, 20 Aug 2024 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/fauxrpc/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/fauxrpc/cover_hu_39af42cea62050f8.webp" /> &lt;/p>
                
                I made a server that outputs nonsense.
                </description><content:encoded><![CDATA[<p>I would like to introduce <strong><a href="http://fauxrpc.com/" rel="external">FauxRPC</a></strong>, a powerful tool that empowers you to accelerate development and testing by effortlessly generating fake implementations of gRPC, gRPC-Web, Connect, and REST services. If you have a <a href="https://kmcd.dev/posts/api-contracts/">protobuf-based workflow</a>, this tool could help.</p>
<h2 id="why-fauxrpc">Why FauxRPC?</h2>
<ul>
<li><strong>Faster Development &amp; Testing:</strong> Work independently without relying on fully functional backend services.</li>
<li><strong>Isolation &amp; Control:</strong> Test frontend components in isolation with controlled fake data.</li>
<li><strong>Multi-Protocol Support:</strong> Supports multiple protocols (gRPC, gRPC-Web, Connect, and REST).</li>
<li><strong>Prototyping &amp; Demos:</strong> Create prototypes and demos quickly without building the full backend. Fake it till you make it.</li>
<li><strong>Improved Collaboration:</strong> Bridge the gap between frontend and backend teams.</li>
<li><strong>Plays well with others:</strong> Test data from FauxRPC will try to automatically follow any <a href="https://github.com/bufbuild/protovalidate" rel="external">protovalidate</a> constraints that are defined.</li>
</ul>
<h2 id="how-it-works">How it Works</h2>
<p>FauxRPC leverages your Protobuf definitions to generate fake services that mimic the behavior of real ones. You can easily configure the fake data returned, allowing you to simulate various scenarios and edge cases. It takes in <code>*.proto</code> files or protobuf descriptors (in binpb, json, txtpb, yaml formats), then it automatically starts up a server that can speak gRPC/gRPC-Web/Connect and REST (as long as there are <code>google.api.http</code> annotations defined). Descriptors contain all of the information found in a set of <code>.proto</code> files. You can generate them with <code>protoc</code> or the <code>buf build</code> command.</p>
<div class="diagram-wrapper">
    <span class="diagram"
        style="">

    
    
        
        
        <img src="https://kmcd.dev/posts/fauxrpc/diagram.svg" class="center" width="800px"/>
    



    </span>

    
</div>

<h2 id="get-started">Get Started</h2>
<p>FauxRPC is available as an open-source project. Check out <a href="https://fauxrpc.com/docs/intro/" rel="external">the documentation</a> and examples to get started. Here&rsquo;s a quick overview, but be sure to check the official documentation for the most up-to-date instructions:</p>
<h3 id="install-via-source">Install via source</h3>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>go install github.com/sudorandom/fauxrpc/cmd/fauxrpc@latest
</span></span></code></pre></div><h3 id="pre-built-binaries">Pre-built binaries</h3>
<p>Binaries are built for several platforms for each release. See the latest ones on <a href="https://github.com/sudorandom/fauxrpc/releases/latest" rel="external">the releases page</a>.</p>
<h3 id="use-descriptors">Use Descriptors</h3>
<p>Make an <code>example.proto</code> file (or use a file that already exists):</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">greet</span><span style="color:#81a1c1">.</span>v1<span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GreetRequest</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GreetResponse</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> greeting <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">service</span> GreetService <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">rpc</span> Greet<span style="color:#eceff4">(</span>GreetRequest<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span> <span style="color:#eceff4">(</span>GreetResponse<span style="color:#eceff4">)</span> <span style="color:#eceff4">{}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>Create a descriptors file and use it to start the FauxRPC server:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf build ./example.proto -o ./example.binpb
</span></span><span style="display:flex;"><span>$ fauxrpc run --schema<span style="color:#81a1c1">=</span>./example.binpb
</span></span><span style="display:flex;"><span>2024/08/17 08:01:19 INFO Listening on http://127.0.0.1:6660
</span></span><span style="display:flex;"><span>2024/08/17 08:01:19 INFO See available methods: buf curl --http2-prior-knowledge http://127.0.0.1:6660 --list-methods
</span></span></code></pre></div><p>Done! It&rsquo;s that easy. Now you can call the service with any tooling that supports gRPC, gRPC-Web, or connect. So <a href="https://buf.build/docs/reference/cli/buf/curl" rel="external">buf curl</a>, <a href="https://github.com/fullstorydev/grpcurl" rel="external">grpcurl</a>, <a href="https://www.postman.com/" rel="external">Postman</a>, <a href="https://insomnia.rest/" rel="external">Insomnia</a> all work fine!</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl --http2-prior-knowledge http://127.0.0.1:6660/greet.v1.GreetService/Greet
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;greeting&#34;</span>: <span style="color:#a3be8c">&#34;dream&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><h3 id="server-reflection">Server Reflection</h3>
<p>If there&rsquo;s an existing gRPC service running that you want to emulate, you can use server reflection to start the FauxRPC service:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ fauxrpc run --schema<span style="color:#81a1c1">=</span>https://demo.connectrpc.com
</span></span></code></pre></div><h3 id="from-bsr-buf-schema-registry">From BSR (Buf Schema Registry)</h3>
<p>Buf has a <a href="https://buf.build/product/bsr" rel="external">schema registry</a> where many schemas are hosted. Here&rsquo;s how to use FauxRPC using images from the registry.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf build buf.build/bufbuild/registry -o bufbuild.registry.json
</span></span><span style="display:flex;"><span>$ fauxrpc run --schema<span style="color:#81a1c1">=</span>./bufbuild.registry.json
</span></span></code></pre></div><h3 id="multiple-sources">Multiple Sources</h3>
<p>You can define this <code>--schema</code> option as many times as you want. That means you can add services from multiple descriptors and even mix and match from descriptors and from server reflection:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ fauxrpc run --schema<span style="color:#81a1c1">=</span>https://demo.connectrpc.com --schema<span style="color:#81a1c1">=</span>./example.binpb
</span></span></code></pre></div><h2 id="multi-protocol-support">Multi-protocol Support</h2>
<p>The multi-protocol support <a href="https://connectrpc.com/docs/multi-protocol/" rel="external">is based on ConnectRPC</a>. So with FauxRPC, you get <strong>gRPC, gRPC-Web and Connect</strong> out of the box. However, FauxRPC does one thing more. It allows you to use <a href="https://grpc-ecosystem.github.io/grpc-gateway/docs/tutorials/adding_annotations/" rel="external"><code>google.api.http</code> annotations</a> to present a JSON/HTTP API, so you can gRPC and REST together! This is normally done with <a href="https://github.com/grpc-ecosystem/grpc-gateway" rel="external">an additional service</a> that runs in-between the outside world and your actual gRPC service but with FauxRPC you get the so-called transcoding from HTTP/JSON to gRPC all in the same package. Here&rsquo;s a concrete example:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">http</span><span style="color:#81a1c1">.</span>service<span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#a3be8c">&#34;google/api/annotations.proto&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">service</span> HTTPService <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">rpc</span> GetMessage<span style="color:#eceff4">(</span>GetMessageRequest<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span> <span style="color:#eceff4">(</span>Message<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">option</span> <span style="color:#eceff4">(</span>google.api.http<span style="color:#eceff4">)</span> <span style="color:#81a1c1">=</span> <span style="color:#eceff4">{</span>get<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#34;/v1/{name=messages/*}&#34;</span><span style="color:#eceff4">};</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GetMessageRequest</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span> <span style="color:#616e87;font-style:italic">// Mapped to URL path.
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">Message</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> text <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span> <span style="color:#616e87;font-style:italic">// The resource content.
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>Again, we start the service by building the descriptors and using</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>$ buf build ./httpservice.proto -o ./httpservice.binpb
</span></span><span style="display:flex;"><span>$ fauxrpc run --schema=httpservice.binpb
</span></span></code></pre></div><p>Now that we have the server running we can test this with the &ldquo;normal&rdquo; curl:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl http://127.0.0.1:6660/v1/messages/123456
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span><span style="color:#a3be8c">&#34;text&#34;</span>:<span style="color:#a3be8c">&#34;Retro.&#34;</span><span style="color:#81a1c1">}</span>⏎
</span></span></code></pre></div><p>Sweet. You can now easily support REST alongside gRPC. If you are wondering how to do this with &ldquo;real&rdquo; services, look into <a href="https://github.com/connectrpc/vanguard-go" rel="external">vanguard-go</a>. This library is doing the real heavy lifting.</p>
<h2 id="what-does-the-fake-data-look-like">What does the fake data look like?</h2>
<p>You might be wondering what actual responses look like. FauxRPC&rsquo;s fake data generation is continually improving so these details might change as time goes on. It uses a library called <a href="https://github.com/brianvoe/gofakeit" rel="external">fakeit</a> to generate fake data. Because protobufs have pretty well-defined types, we can easily generate data that technically matches the types. This works well for most use cases, but FauxRPC tries to be a little bit better. If you annotate your protobuf files with <a href="https://github.com/bufbuild/protovalidate" rel="external">protovalidate</a> constraints, FauxRPC will try its best to generate data that matches these constraints. Let&rsquo;s look at some examples!</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">greet</span><span style="color:#81a1c1">.</span>v1<span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GreetRequest</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GreetResponse</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> greeting <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">service</span> GreetService <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">rpc</span> Greet<span style="color:#eceff4">(</span>GreetRequest<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span> <span style="color:#eceff4">(</span>GreetResponse<span style="color:#eceff4">)</span> <span style="color:#eceff4">{}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>With FauxRPC, you will get any kind of word, so it might look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;greeting&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;sufficient&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>This is fine, but for the RPC, we know a bit more about the type being returned. We know that it sends a greeting back that looks like &ldquo;Hello, [name]&rdquo;. So here&rsquo;s what the same protobuf file might look like with protovalidate constraints:</p>
<p>Now let&rsquo;s see what this looks like with protovalidate constraints:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#a3be8c">&#34;buf/validate/validate.proto&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">greet</span><span style="color:#81a1c1">.</span>v1<span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GreetRequest</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span> <span style="color:#eceff4">[(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1">string</span> <span style="color:#81a1c1">=</span> <span style="color:#eceff4">{</span>min_len<span style="color:#81a1c1">:</span> <span style="color:#b48ead">3</span><span style="color:#eceff4">,</span> max_len<span style="color:#81a1c1">:</span> <span style="color:#b48ead">100</span><span style="color:#eceff4">}];</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GreetResponse</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> greeting <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span> <span style="color:#eceff4">[(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1">string</span><span style="color:#81a1c1">.</span>pattern <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;^Hello, [a-zA-Z]+$&#34;</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">service</span> GreetService <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">rpc</span> Greet<span style="color:#eceff4">(</span>GreetRequest<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span> <span style="color:#eceff4">(</span>GreetResponse<span style="color:#eceff4">)</span> <span style="color:#eceff4">{}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>With this new protobuf file, this is what FauxRPC might output now:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">&#34;greeting&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Hello, TWXxF&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>This shows how protovalidate constraints enable FauxRPC to generate more realistic and contextually relevant fake data, aligning it closer to the expected behavior of your actual services. As another example, I will show one of Buf&rsquo;s services used to manage users, <a href="https://buf.build/bufbuild/registry/docs/main:buf.registry.owner.v1#buf.registry.owner.v1.UserService" rel="external">buf.registry.owner.v1.UserService</a>. Here&rsquo;s what the <code>UserRef</code> message looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">UserRef</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">option</span> <span style="color:#eceff4">(</span>buf.registry.priv.extension.v1beta1.message<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span>request_only <span style="color:#81a1c1">=</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">oneof</span> value <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">option</span> <span style="color:#eceff4">(</span>buf.validate.oneof<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1;font-weight:bold">required</span> <span style="color:#81a1c1">=</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#616e87;font-style:italic">// The id of the User.
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span>    <span style="color:#81a1c1">string</span> id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span> <span style="color:#eceff4">[(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1">string</span><span style="color:#81a1c1">.</span>tuuid <span style="color:#81a1c1">=</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">];</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#616e87;font-style:italic">// The name of the User.
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span>    <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span> <span style="color:#eceff4">[(</span>buf.validate.field<span style="color:#eceff4">)</span><span style="color:#81a1c1">.</span><span style="color:#81a1c1">string</span> <span style="color:#81a1c1">=</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>      min_len<span style="color:#81a1c1">:</span> <span style="color:#b48ead">2</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>      max_len<span style="color:#81a1c1">:</span> <span style="color:#b48ead">32</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>      pattern<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#34;^[a-z][a-z0-9-]*[a-z0-9]$&#34;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#eceff4">}];</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>So let&rsquo;s make our descriptors for this service, start the FauxRPC server and make our example request:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf build buf.build/bufbuild/registry -o bufbuild.registry.binpb
</span></span><span style="display:flex;"><span>$ fauxrpc run --schema<span style="color:#81a1c1">=</span>./bufbuild.registry.binpb
</span></span><span style="display:flex;"><span>$ buf curl --http2-prior-knowledge http://127.0.0.1:6660/buf.registry.owner.v1.UserService/ListUsers
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;nextPageToken&#34;</span>: <span style="color:#a3be8c">&#34;Food truck.&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;users&#34;</span>: <span style="color:#81a1c1">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;id&#34;</span>: <span style="color:#a3be8c">&#34;c4468393f926400d8880a264df9c284a&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;createTime&#34;</span>: <span style="color:#a3be8c">&#34;2012-03-06T12:15:03.239463070Z&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;updateTime&#34;</span>: <span style="color:#a3be8c">&#34;1990-10-29T13:12:31.224347086Z&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;name&#34;</span>: <span style="color:#a3be8c">&#34;jexox&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;type&#34;</span>: <span style="color:#a3be8c">&#34;USER_TYPE_STANDARD&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;description&#34;</span>: <span style="color:#a3be8c">&#34;Tattooed taxidermy.&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;url&#34;</span>: <span style="color:#a3be8c">&#34;http://www.productexploit.name/synergies/target&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">}</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;id&#34;</span>: <span style="color:#a3be8c">&#34;0e4ca24f4ff54761b109daab0da1bea2&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;createTime&#34;</span>: <span style="color:#a3be8c">&#34;1955-05-16T02:37:30.643378679Z&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;updateTime&#34;</span>: <span style="color:#a3be8c">&#34;1923-08-28T04:28:43.330711919Z&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;name&#34;</span>: <span style="color:#a3be8c">&#34;ya0&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;type&#34;</span>: <span style="color:#a3be8c">&#34;USER_TYPE_STANDARD&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;state&#34;</span>: <span style="color:#a3be8c">&#34;USER_STATE_INACTIVE&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;description&#34;</span>: <span style="color:#a3be8c">&#34;Helvetica.&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;url&#34;</span>: <span style="color:#a3be8c">&#34;https://www.centralengage.info/markets/scale/e-commerce/exploit&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#34;verificationStatus&#34;</span>: <span style="color:#a3be8c">&#34;USER_VERIFICATION_STATUS_UNVERIFIED&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>Hopefully, this gives you a good idea of what the output might look like. The better your validation rules, the better the FauxRPC data will be.</p>
<h2 id="whats-left">What&rsquo;s left?</h2>
<p>FauxRPC is already great for some use cases but it&rsquo;s not &ldquo;done&rdquo; as there&rsquo;s more to do to make it better. I have plans to add the ability to configure stubs for each RPC method. This will allow you to define specific responses or behaviors for each RPC, giving you more control over the simulated service. I hope this will make it easier to iterate on protobuf designs without needing to actually implement services until later.</p>
<h2 id="stay-tuned">Stay Tuned</h2>
<p>I made a <a href="http://fauxrpc.com/" rel="external">documentation website</a> to organize documentation. I think it looks pretty good for how quickly I threw it together. The code for FauxRPC lives on GitHub at <a href="https://github.com/sudorandom/fauxrpc" rel="external">sudorandom/fauxrpc</a>. It&rsquo;s a little thin now but there&rsquo;s a lot that I can write about in there. I&rsquo;m actively developing FauxRPC and have many exciting features planned for the future. This is early on for this project but it has come together as a coherent and useful program for me extremely quickly. So please try it out and let me know your feedback and suggestions. Stay tuned for updates!</p>
<p><em>&hellip; and don&rsquo;t forget to <a href="https://github.com/sudorandom/fauxrpc" rel="external">star the repo on GitHub</a>. It helps more than you know!</em></p>
]]></content:encoded></item><item><title>HTTP/1.0 From Scratch</title><link>https://kmcd.dev/posts/http1.0-from-scratch/</link><pubDate>Tue, 13 Aug 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/http1.0-from-scratch/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/http1.0-from-scratch/cover_hu_2088ae23d8169bf0.webp" /> &lt;/p>
                
                Laying the Foundation: Building the Web with HTTP/1.0.
                </description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>In our previous exploration, we delved into the simplicity of <a href="https://kmcd.dev/posts/http0.9-from-scratch">HTTP/0.9</a>, a protocol that served as the web&rsquo;s initial foundation. However, as the internet evolved, so did its needs. Enter HTTP/1.0, a landmark version released in 1996 that laid the groundwork for the web we know today. HTTP/1.0 was a game-changer, introducing features that revolutionized web communication:</p>
<ul>
<li><strong>Headers:</strong> Metadata that added context and control to requests and responses (<a href="https://datatracker.ietf.org/doc/html/rfc1945#section-4.2" rel="external">RFC 1945 4.2</a>).</li>
<li><strong>Methods:</strong> A diverse set of actions (POST, HEAD, PUT, DELETE, etc.) beyond just retrieving documents. The web was no longer read-only. (<a href="https://datatracker.ietf.org/doc/html/rfc1945#section-8" rel="external">RFC 1945 8</a>).</li>
<li><strong>Status Codes:</strong> Clear signals about the outcome of requests, paving the way for better error handling and redirection (<a href="https://datatracker.ietf.org/doc/html/rfc1945#section-6.1.1" rel="external">RFC 1945 6.1.1</a>).</li>
<li><strong>Content Negotiation:</strong> The ability to request specific formats (<a href="https://datatracker.ietf.org/doc/html/rfc1945#section-10.5" rel="external">RFC 1945 10.5</a>), encoding (<a href="https://datatracker.ietf.org/doc/html/rfc1945#section-10.3" rel="external">RFC 1945 10.3</a>) or languages (<a href="https://datatracker.ietf.org/doc/html/rfc1945#appendix-D.2.5" rel="external">RFC 1945 D.2.5</a>) for content.</li>
</ul>
<p>In this article, we&rsquo;ll journey through the intricacies of HTTP/1.0 and craft a simple Go server that speaks the protocol.</p>
<h2 id="understanding-http10">Understanding HTTP/1.0</h2>
<p>First, let&rsquo;s cover the new features added in HTTP/1.0. I hope at the end of this section you&rsquo;ll understand why this version was truly deserving of the &ldquo;1.0&rdquo; labeling.</p>
<h3 id="headers">Headers</h3>
<p>Headers convey vital information about requests and responses. Some common headers include:</p>
<ul>
<li><code>Content-Type</code>:  Indicates the format of the data (text/html, image/jpeg, etc.). (<a href="https://datatracker.ietf.org/doc/html/rfc1945#section-10.5" rel="external">RFC 1945 10.5</a>)</li>
<li><code>Accept</code>:  Tells the server which content type the client expects. (<a href="https://datatracker.ietf.org/doc/html/rfc1945#appendix-D.2.1" rel="external">RFC 1945 D2.1</a>)</li>
<li><code>Content-Length</code>: Specifies the size of the response body. (<a href="https://datatracker.ietf.org/doc/html/rfc1945#section-10.4" rel="external">RFC 1945 10.4</a>)</li>
<li><code>User-Agent</code>: Identifies the client software making the request. (<a href="https://datatracker.ietf.org/doc/html/rfc1945#section-10.15" rel="external">RFC 1945 10.15</a>)</li>
</ul>
<p>&hellip;but many more have since become standard.</p>
<h3 id="http-methods">HTTP Methods</h3>
<p>HTTP/1.0 introduced a variety of methods:</p>
<ul>
<li><strong>GET:</strong>  Requests a resource.</li>
<li><strong>POST:</strong>  Submits data to be processed by the server.</li>
<li><strong>HEAD:</strong>  Similar to GET, but only requests the headers, not the body.</li>
<li><strong>PUT:</strong> Replaces an existing resource or creates a new one if it doesn&rsquo;t exist.</li>
<li><strong>DELETE:</strong>  Removes a specified resource.</li>
</ul>
<h3 id="status-codes">Status Codes</h3>
<p>Status codes are essential for communication between the client and server. They fall into categories:</p>
<ul>
<li><strong>1xx:</strong> <a href="https://datatracker.ietf.org/doc/html/rfc1945#section-9.1" rel="external">Informational.</a></li>
<li><strong>2xx:</strong> <a href="https://datatracker.ietf.org/doc/html/rfc1945#section-9.2" rel="external">Success.</a></li>
<li><strong>3xx:</strong> <a href="https://datatracker.ietf.org/doc/html/rfc1945#section-9.3" rel="external">Redirection</a>.</li>
<li><strong>4xx:</strong> <a href="https://datatracker.ietf.org/doc/html/rfc1945#section-9.4" rel="external">Client Error (e.g., 404 Not Found).</a></li>
<li><strong>5xx:</strong> <a href="https://datatracker.ietf.org/doc/html/rfc1945#section-9.5" rel="external">Server Error (e.g., 500 Internal Server Error).</a></li>
</ul>
<h3 id="request-structure">Request Structure</h3>
<p>HTTP/1.0 requests follow a structured format:</p>
<ol>
<li><strong>Request Line:</strong> Specifies the HTTP method (e.g., GET, POST), the requested path, and the protocol version (HTTP/1.0).</li>
<li><strong>Headers:</strong> Key-value pairs that provide additional information (e.g., <code>User-Agent</code>, <code>Content-Type</code>, <code>Referer</code>).</li>
<li><strong>Empty Line:</strong> Signals the end of the headers.</li>
<li><strong>Request Body (Optional):</strong> Data sent with the request (common with POST).</li>
</ol>
<p>Headers and request bodies are completely new in this version. Now there is an official way to send data to servers using HTTP. Before this, it was possible to encode information into the HTTP path to send data to the servers but now that HTTP servers can accept entire documents from clients, it opens up the possibilities for more dynamic web experiences. And don&rsquo;t sleep on headers. Headers provide a way to annotate more details about web requests that would be very tedious to add in the URL. Now the client can specify the language, content-encoding, character-set, content-type that it will accept as a response and much, much more.</p>
<h4 id="example">Example</h4>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#88c0d0">GET</span> <span style="color:#8fbcbb">/index.html</span> <span style="color:#81a1c1;font-weight:bold">HTTP</span><span style="color:#81a1c1">/</span><span style="color:#b48ead">1.0</span>
</span></span><span style="display:flex;"><span>User-Agent<span style="color:#81a1c1">:</span> Mozilla/5.0
</span></span><span style="display:flex;"><span>Host<span style="color:#81a1c1">:</span> www.example.com
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(Optional request body)
</span></span></code></pre></div><h4 id="response-structure">Response Structure</h4>
<p>HTTP/1.0 responses mirror this structure:</p>
<ol>
<li><strong>Status Line:</strong>  Includes the protocol version, a status code (e.g., 200 OK, 404 Not Found), and a reason phrase. (<a href="https://datatracker.ietf.org/doc/html/rfc1945#section-6.1" rel="external">RFC 1945 6.1</a>)</li>
<li><strong>Headers:</strong> Similar to request headers, providing metadata about the response. (<a href="https://datatracker.ietf.org/doc/html/rfc1945#section-6.2" rel="external">RFC 1945 6.2</a>)</li>
<li><strong>Empty Line:</strong> Separates headers from the body.</li>
<li><strong>Response Body:</strong> The actual content sent back to the client.</li>
</ol>
<p>Status codes now enable more robust error handling. Web servers would use the status codes to show appropriate error pages to the user. These error codes have been so important that non-technical people around the world will know of <code>404 Not Found</code>.</p>
<p>Now HTTP responses return headers. This adds important information about how to render the document. Finally, we&rsquo;re not just limited to HTML. Headers are critical to many features today, including redirection, caching, authentication, authorization, and many more.</p>
<h4 id="example-1">Example</h4>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">HTTP</span><span style="color:#81a1c1">/</span><span style="color:#b48ead">1.0</span> <span style="color:#b48ead">200</span> <span style="color:#bf616a">OK</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#81a1c1">:</span> text/html
</span></span><span style="display:flex;"><span>Content-Length<span style="color:#81a1c1">:</span> 1354
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(HTML content here)
</span></span></code></pre></div><p>Nothing has changed with the sequence of events from HTTP/0.9. It still looks like this:</p>
<div class="container">
  <pre class="mermaid">sequenceDiagram
    actor Client

    rect rgb(47,75,124)
        Client ->> Server: TCP SYN
        Server ->> Client: TCP SYN-ACK
        Client ->> Server: TCP ACK
    end

    rect rgb(200,80,96)
        Client ->> Server: HTTP Request
        Server ->> Client: HTTP Response
    end

    rect rgb(47,75,124)
        Server ->> Client: TCP FIN
		Client ->> Server: TCP ACK
    end
  </pre>
</div>
<h3 id="caching">Caching</h3>
<p>Now that request and response headers exist, it&rsquo;s possible to negotiate caching behavior, which is well-defined in the HTTP/1.0 spec. It defines which methods can be cached and which headers to use to define the behavior. While it&rsquo;s an optional feature for servers, it was very important in the early days of the web and I would say that it&rsquo;s even more important today.</p>
<h3 id="host-header">Host Header</h3>
<p>One thing that goes overlooked is that goes overlooked is that <code>Host</code> header but, arguably, it&rsquo;s what made the early web possible. With that one header, one web server can host many different websites using the same IP public address and post. It made the cost of running a website on your domain more reasonable and it created an industry of reseller web hosts.</p>
<h2 id="implementing-an-http10-server-in-go">Implementing an HTTP/1.0 Server in Go</h2>
<p>Enough theory! Let&rsquo;s roll up our sleeves and bring HTTP/1.0 to life.</p>
<pre><code>Programming is learned by writing programs. — Brian Kernighan
</code></pre>
<p>We&rsquo;ll build a simple Go server from the ground up, handling requests and responses with the elegance and efficiency that Go is known for. By the end of this section, you&rsquo;ll have a working HTTP/1.0 server that you can interact with using familiar tools like <code>curl</code> and your web browser. Note that this server is NOT &ldquo;production ready&rdquo; and is only meant for learning. Many aspects of HTTP are not clearly defined in the spec that are critical to get right to avoid security exploits and denial of service attacks.</p>
<p>If you&rsquo;d rather just dig into the code yourself, Go here: <a href="https://github.com/sudorandom/kmcd.dev/blob/main/content/posts/2024/http1.0-from-scratch/go/server/main.go" target="_blank">main.go</a>. Some people (like me) learn better by just downloading the entire script and modifying it to see what breaks to get a better understanding.</p>
<p>The heart of our HTTP/1.0 server is this Server struct, which encapsulates the server&rsquo;s address and the handler responsible for processing incoming requests. The <code>ListenAndServe()</code> method initiates the server, listens for connections, and handles each one concurrently, in a new goroutine.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> Server <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Addr    <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span>	Handler http<span style="color:#eceff4">.</span>Handler
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>s <span style="color:#81a1c1">*</span>Server<span style="color:#eceff4">)</span> <span style="color:#88c0d0">ListenAndServe</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	handler <span style="color:#81a1c1">:=</span> s<span style="color:#eceff4">.</span>Handler
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> handler <span style="color:#81a1c1">==</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		handler <span style="color:#eceff4">=</span> http<span style="color:#eceff4">.</span>DefaultServeMux
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	l<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> net<span style="color:#eceff4">.</span><span style="color:#88c0d0">Listen</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;tcp&#34;</span><span style="color:#eceff4">,</span> s<span style="color:#eceff4">.</span>Addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> l<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> l<span style="color:#eceff4">.</span><span style="color:#88c0d0">Accept</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">go</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> s<span style="color:#eceff4">.</span><span style="color:#88c0d0">handleConnection</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Error</span><span style="color:#eceff4">(</span>fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sprintf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;http error: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>You might notice that it&rsquo;s mostly unchanged from the 0.9 version. But as a recap, this code sets up a TCP listener on the specified address, starts an infinite loop, accepts connections and calls <code>s.handleConnection</code> in a new goroutine.</p>
<p>Now we&rsquo;re moving to the heart of the HTTP server. The <code>handleConnection</code> method parses the incoming HTTP request, extracting the method, path, headers, and optional body. It then passes the constructed <code>http.Request</code> to the server&rsquo;s handler for further processing and response generation.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>s <span style="color:#81a1c1">*</span>Server<span style="color:#eceff4">)</span> <span style="color:#88c0d0">handleConnection</span><span style="color:#eceff4">(</span>conn net<span style="color:#eceff4">.</span>Conn<span style="color:#eceff4">)</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Limit headers to 1MB</span>
</span></span><span style="display:flex;"><span>	limitReader <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">LimitReader</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">,</span> <span style="color:#b48ead">1</span><span style="color:#81a1c1">*</span><span style="color:#b48ead">1024</span><span style="color:#81a1c1">*</span><span style="color:#b48ead">1024</span><span style="color:#eceff4">).(</span><span style="color:#81a1c1">*</span>io<span style="color:#eceff4">.</span>LimitedReader<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	reader <span style="color:#81a1c1">:=</span> bufio<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>limitReader<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	headerReader <span style="color:#81a1c1">:=</span> textproto<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>bufio<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>reader<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Read the request line: GET /path/to/index.html HTTP/1.0</span>
</span></span><span style="display:flex;"><span>	reqLine<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> headerReader<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadLine</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;read request line error: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	req <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">new</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> found <span style="color:#81a1c1">bool</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Parse Method: GET/POST/PUT/DELETE/etc</span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>Method<span style="color:#eceff4">,</span> reqLine<span style="color:#eceff4">,</span> found <span style="color:#eceff4">=</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">Cut</span><span style="color:#eceff4">(</span>reqLine<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34; &#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>found <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid method&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span><span style="color:#88c0d0">methodValid</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">.</span>Method<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid method&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Parse Request URI</span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>RequestURI<span style="color:#eceff4">,</span> reqLine<span style="color:#eceff4">,</span> found <span style="color:#eceff4">=</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">Cut</span><span style="color:#eceff4">(</span>reqLine<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34; &#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>found <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid path&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> req<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">,</span> err <span style="color:#eceff4">=</span> url<span style="color:#eceff4">.</span><span style="color:#88c0d0">ParseRequestURI</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">.</span>RequestURI<span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid path: %w&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Parse protocol version &#34;HTTP/1.0&#34;</span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>Proto <span style="color:#eceff4">=</span> reqLine
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>ProtoMajor<span style="color:#eceff4">,</span> req<span style="color:#eceff4">.</span>ProtoMinor<span style="color:#eceff4">,</span> found <span style="color:#eceff4">=</span> <span style="color:#88c0d0">parseProtocol</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">.</span>Proto<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>found <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid proto&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Parse headers</span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>Header <span style="color:#eceff4">=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		line<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> headerReader<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadLineBytes</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#81a1c1">&amp;&amp;</span> err <span style="color:#81a1c1">!=</span> io<span style="color:#eceff4">.</span>EOF <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>line<span style="color:#eceff4">)</span> <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		k<span style="color:#eceff4">,</span> v<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> bytes<span style="color:#eceff4">.</span><span style="color:#88c0d0">Cut</span><span style="color:#eceff4">(</span>line<span style="color:#eceff4">,</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#39;:&#39;</span><span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;invalid header&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		req<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span>strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">ToLower</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>k<span style="color:#eceff4">)),</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">TrimLeft</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>v<span style="color:#eceff4">),</span> <span style="color:#a3be8c">&#34; &#34;</span><span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Unbound the limit after we&#39;ve read the headers since the body can be any size</span>
</span></span><span style="display:flex;"><span>	limitReader<span style="color:#eceff4">.</span>N <span style="color:#eceff4">=</span> math<span style="color:#eceff4">.</span>MaxInt64
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	ctx <span style="color:#81a1c1">:=</span> context<span style="color:#eceff4">.</span><span style="color:#88c0d0">Background</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	ctx <span style="color:#eceff4">=</span> context<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithValue</span><span style="color:#eceff4">(</span>ctx<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>LocalAddrContextKey<span style="color:#eceff4">,</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">LocalAddr</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>	ctx<span style="color:#eceff4">,</span> cancelCtx <span style="color:#81a1c1">:=</span> context<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithCancel</span><span style="color:#eceff4">(</span>ctx<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> <span style="color:#88c0d0">cancelCtx</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	contentLength<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> <span style="color:#88c0d0">parseContentLength</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span><span style="color:#88c0d0">Get</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Content-Length&#34;</span><span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>ContentLength <span style="color:#eceff4">=</span> contentLength
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> req<span style="color:#eceff4">.</span>ContentLength <span style="color:#81a1c1">==</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		req<span style="color:#eceff4">.</span>Body <span style="color:#eceff4">=</span> noBody<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		req<span style="color:#eceff4">.</span>Body <span style="color:#eceff4">=</span> <span style="color:#81a1c1">&amp;</span>bodyReader<span style="color:#eceff4">{</span>reader<span style="color:#eceff4">:</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">LimitReader</span><span style="color:#eceff4">(</span>reader<span style="color:#eceff4">,</span> req<span style="color:#eceff4">.</span>ContentLength<span style="color:#eceff4">)}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>RemoteAddr <span style="color:#eceff4">=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">RemoteAddr</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">String</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>Close <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span> <span style="color:#616e87;font-style:italic">// this is always true for HTTP/1.0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	w <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>responseBodyWriter<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// We hard-code this because this is a HTTP/1.0 server.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Web servers will make requests with HTTP/1.1 but</span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// we&#39;re saying that we only support HTTP/1.0.</span>
</span></span><span style="display:flex;"><span>		proto<span style="color:#eceff4">:</span>   <span style="color:#a3be8c">&#34;HTTP/1.0&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">:</span>    conn<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		headers<span style="color:#eceff4">:</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Finally, call our http.Handler!</span>
</span></span><span style="display:flex;"><span>	s<span style="color:#eceff4">.</span>Handler<span style="color:#eceff4">.</span><span style="color:#88c0d0">ServeHTTP</span><span style="color:#eceff4">(</span>w<span style="color:#eceff4">,</span> req<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithContext</span><span style="color:#eceff4">(</span>ctx<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>w<span style="color:#eceff4">.</span>sentHeaders <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		w<span style="color:#eceff4">.</span><span style="color:#88c0d0">sendHeaders</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>StatusOK<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><ul>
<li>Creates a fresh <code>*http.Request</code> object that we&rsquo;ll pass to the given <code>http.Handler</code></li>
<li>Reads and parses the request line (method, path, protocol) <code>GET /path/to/index.html HTTP/1.0</code></li>
<li>Parses and stores request headers</li>
<li>Handles the request body based on the Content-Length header</li>
<li>Creates a <code>responseBodyWriter</code> to handle writing the response headers and body</li>
<li>Calls the server&rsquo;s Handler to process the request and generate the response</li>
<li>Sends the response headers if not already sent.</li>
</ul>
<p>Now let&rsquo;s dig into the utility functions that we just called. First, here&rsquo;s a simple one. If the client didn&rsquo;t send a HTTP body, we still need to populate <code>request.Body</code> with <em>something</em>, so this is a mocked-out version that satisfies the required <code>io.ReadCloser</code> interface.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> noBody <span style="color:#81a1c1;font-weight:bold">struct</span><span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>noBody<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Read</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span>         <span style="color:#eceff4">{</span> <span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> io<span style="color:#eceff4">.</span>EOF <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>noBody<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span>                     <span style="color:#eceff4">{</span> <span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Next, we have the type that we use for <code>request.Body</code> when there&rsquo;s a request body. It is fairly simple:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> bodyReader <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	reader io<span style="color:#eceff4">.</span>Reader
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>bodyReader<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Read</span><span style="color:#eceff4">(</span>p <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>n <span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> err <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> r<span style="color:#eceff4">.</span>reader<span style="color:#eceff4">.</span><span style="color:#88c0d0">Read</span><span style="color:#eceff4">(</span>p<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>bodyReader<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	_<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">Copy</span><span style="color:#eceff4">(</span>io<span style="color:#eceff4">.</span>Discard<span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>reader<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Here are assorted helper functions that don&rsquo;t have too much to comment on. I had to add HTTP/1.1 to parseProtocol because many browsers and other tools will send requests as HTTP/1.1 because there&rsquo;s no protocol version negotiation before a request to tell HTTP/1.0 and HTTP/1.1 apart.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">parseContentLength</span><span style="color:#eceff4">(</span>headerval <span style="color:#81a1c1">string</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int64</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> headerval <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> strconv<span style="color:#eceff4">.</span><span style="color:#88c0d0">ParseInt</span><span style="color:#eceff4">(</span>headerval<span style="color:#eceff4">,</span> <span style="color:#b48ead">10</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">64</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">parseProtocol</span><span style="color:#eceff4">(</span>proto <span style="color:#81a1c1">string</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">bool</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">switch</span> proto <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">case</span> <span style="color:#a3be8c">&#34;HTTP/1.0&#34;</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">case</span> <span style="color:#a3be8c">&#34;HTTP/1.1&#34;</span><span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">methodValid</span><span style="color:#eceff4">(</span>method <span style="color:#81a1c1">string</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">bool</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">switch</span> method <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">case</span> http<span style="color:#eceff4">.</span>MethodGet<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodHead<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodPost<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodPut<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodPatch<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodDelete<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodConnect<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodOptions<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span>MethodTrace<span style="color:#eceff4">:</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>And now we have the response body writer. So far, all of the code has been in aid for creating an <code>http.Request</code> object, but handlers also need a <code>http.ResponseWriter</code> to satisfy the handler interface:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> Handler <span style="color:#81a1c1;font-weight:bold">interface</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#88c0d0">ServeHTTP</span><span style="color:#eceff4">(</span>ResponseWriter<span style="color:#eceff4">,</span> <span style="color:#81a1c1">*</span>Request<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Here&rsquo;s the implementation:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> responseBodyWriter <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	proto       <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span>	conn        net<span style="color:#eceff4">.</span>Conn
</span></span><span style="display:flex;"><span>	sentHeaders <span style="color:#81a1c1">bool</span>
</span></span><span style="display:flex;"><span>	headers     http<span style="color:#eceff4">.</span>Header
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Header</span><span style="color:#eceff4">()</span> http<span style="color:#eceff4">.</span>Header <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> r<span style="color:#eceff4">.</span>headers
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>b <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#eceff4">!</span>r<span style="color:#eceff4">.</span>sentHeaders <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		r<span style="color:#eceff4">.</span><span style="color:#88c0d0">sendHeaders</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>StatusOK<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">WriteHeader</span><span style="color:#eceff4">(</span>statusCode <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> r<span style="color:#eceff4">.</span>sentHeaders <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Warn</span><span style="color:#eceff4">(</span>fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sprintf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;WriteHeader called twice, second time with: %d&#34;</span><span style="color:#eceff4">,</span> statusCode<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	r<span style="color:#eceff4">.</span><span style="color:#88c0d0">sendHeaders</span><span style="color:#eceff4">(</span>statusCode<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">sendHeaders</span><span style="color:#eceff4">(</span>statusCode <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	r<span style="color:#eceff4">.</span>sentHeaders <span style="color:#eceff4">=</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>	io<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>proto<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#39; &#39;</span><span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>	io<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">,</span> strconv<span style="color:#eceff4">.</span><span style="color:#88c0d0">FormatInt</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">int64</span><span style="color:#eceff4">(</span>statusCode<span style="color:#eceff4">),</span> <span style="color:#b48ead">10</span><span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#39; &#39;</span><span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>	io<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">StatusText</span><span style="color:#eceff4">(</span>statusCode<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#39;\r&#39;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#39;\n&#39;</span><span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> k<span style="color:#eceff4">,</span> vals <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> r<span style="color:#eceff4">.</span>headers <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">for</span> _<span style="color:#eceff4">,</span> val <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> vals <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			io<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">,</span> k<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#39;:&#39;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#39; &#39;</span><span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>			io<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">,</span> val<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#39;\r&#39;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#39;\n&#39;</span><span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{</span><span style="color:#a3be8c">&#39;\r&#39;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#39;\n&#39;</span><span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><ul>
<li><code>Header()</code> allows handlers to add headers to the response.</li>
<li><code>WriteHeader(statusCode int)</code> writes out the status code line and any headers that the handler has added</li>
<li><code>Write(b []byte)</code> writes some data and can be called multiple times for streaming the body back to the client. Note that once you start writing the body, the headers will automatically be sent.</li>
</ul>
<p>And, finally, here&rsquo;s how to start the server, which looks very similar to the last time (and many Go tutorials):</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	addr <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;127.0.0.1:9000&#34;</span>
</span></span><span style="display:flex;"><span>	mux <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServeMux</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// TODO: handlers go here</span>
</span></span><span style="display:flex;"><span>	s <span style="color:#81a1c1">:=</span> Server<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Addr<span style="color:#eceff4">:</span>    addr<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		Handler<span style="color:#eceff4">:</span> mux<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Starting web server: http://%s&#34;</span><span style="color:#eceff4">,</span> addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> s<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServe</span><span style="color:#eceff4">();</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatal</span><span style="color:#eceff4">(</span>err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>That&rsquo;s it. We&rsquo;re done with our server! See the full source at Github: <a href="https://github.com/sudorandom/kmcd.dev/blob/main/content/posts/2024/http1.0-from-scratch/go/server/main.go" target="_blank">main.go</a> to see how it all fits together. Similar to last time, I also wanted to test this implementation to make sure it works well with browsers and other web tools.</p>
<h2 id="testing-the-implementation">Testing the Implementation</h2>
<p>First off, to start the server, you can run this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">go</span> run server<span style="color:#81a1c1">/</span>main<span style="color:#eceff4">.</span><span style="color:#81a1c1;font-weight:bold">go</span>
</span></span></code></pre></div><h3 id="handlers">Handlers</h3>
<p>For this time, I&rsquo;ve added a few more handlers to test different aspects of our HTTP server. It&rsquo;s more fun that way and there are just generally more things to test with HTTP/1.0, like status codes and headers.</p>
<h4 id="headers-1">/headers</h4>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandleFunc</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;/headers&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    w<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;content-type&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;application/json&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    json<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewEncoder</span><span style="color:#eceff4">(</span>w<span style="color:#eceff4">).</span><span style="color:#88c0d0">Encode</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">})</span>
</span></span></code></pre></div><ul>
<li><strong>Description:</strong> This handler echoes back all the request headers in JSON format. It also sets the <code>Content-Type</code> header to <code>application/json</code> to inform the client about the response format.</li>
<li><strong>Testing Rationale:</strong> Useful for verifying that the server correctly receives and parses request headers. Can be used to test sending various headers with different values and case variations.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl -H <span style="color:#a3be8c">&#34;My-Custom-Header: Hello, world!&#34;</span> http://localhost:9000/headers
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span><span style="color:#a3be8c">&#34;Accept&#34;</span>:<span style="color:#81a1c1">[</span><span style="color:#a3be8c">&#34;*/*&#34;</span><span style="color:#81a1c1">]</span>,<span style="color:#a3be8c">&#34;Host&#34;</span>:<span style="color:#81a1c1">[</span><span style="color:#a3be8c">&#34;localhost:9000&#34;</span><span style="color:#81a1c1">]</span>,<span style="color:#a3be8c">&#34;My-Custom-Header&#34;</span>:<span style="color:#81a1c1">[</span><span style="color:#a3be8c">&#34;Hello, world!&#34;</span><span style="color:#81a1c1">]</span>,<span style="color:#a3be8c">&#34;User-Agent&#34;</span>:<span style="color:#81a1c1">[</span><span style="color:#a3be8c">&#34;curl/8.7.1&#34;</span><span style="color:#81a1c1">]}</span>
</span></span></code></pre></div><p>You can even use <a href="https://nc110.sourceforge.io/" rel="external">netcat</a> to craft the HTTP request manually:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#81a1c1">printf</span> <span style="color:#a3be8c">&#34;GET /headers HTTP/1.0\r\nMy-Custom-Header: Hello from nc!\r\n&#34;</span> <span style="color:#eceff4">|</span> nc localhost <span style="color:#b48ead">9000</span>
</span></span></code></pre></div><p>Netcat is particularly useful at testing text-based protocols because you can write out exactly what the request will look like, which is great for testing unique situations that are hard to set up using existing tools like <code>curl</code>.</p>
<h4 id="statusstatus">/status/{status}</h4>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandleFunc</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;/status/{status}&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    status<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> strconv<span style="color:#eceff4">.</span><span style="color:#88c0d0">ParseInt</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">.</span><span style="color:#88c0d0">PathValue</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;status&#34;</span><span style="color:#eceff4">),</span> <span style="color:#b48ead">10</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">64</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        w<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteHeader</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>StatusBadRequest<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>        io<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>w<span style="color:#eceff4">,</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sprintf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;error: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    w<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteHeader</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">(</span>status<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">})</span>
</span></span></code></pre></div><ul>
<li><strong>Description:</strong> This handler allows you to request a specific HTTP status code by providing it in the URL path (e.g., <code>/status/404</code>). If the provided status is invalid, it returns a 400 Bad Request error.</li>
<li><strong>Testing Rationale:</strong> Essential for testing how clients handle different status codes, especially error codes. Can be used to simulate various scenarios like successful requests, redirects, client errors, and server errors.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl -I http://localhost:9000/status/404
</span></span><span style="display:flex;"><span>HTTP/1.0 <span style="color:#b48ead">404</span> Not Found
</span></span></code></pre></div><h4 id="nothing">/nothing</h4>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandleFunc</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;/nothing&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{})</span>
</span></span></code></pre></div><ul>
<li><strong>Description:</strong> This handler does absolutely nothing. It doesn&rsquo;t write any headers or body.</li>
<li><strong>Testing Rationale:</strong> Can be used to test the server&rsquo;s behavior when a handler doesn&rsquo;t explicitly send a response. It should still send a default 200 OK response with no body, as implemented in the <code>responseBodyWriter</code>.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl http://localhost:9000/nothing
</span></span></code></pre></div><h4 id="echo">/echo</h4>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandleFunc</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;/echo&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">defer</span> r<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>    io<span style="color:#eceff4">.</span><span style="color:#88c0d0">Copy</span><span style="color:#eceff4">(</span>w<span style="color:#eceff4">,</span> r<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">})</span>
</span></span></code></pre></div><ul>
<li><strong>Description:</strong> This handler echoes back the request body as the response body.</li>
<li><strong>Testing Rationale:</strong> Useful for testing how the server handles request bodies, especially with POST requests. Can be used to verify that the server correctly receives and processes the body data.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl -X POST -d <span style="color:#a3be8c">&#34;This is some data to echo&#34;</span> http://localhost:9000/echo
</span></span><span style="display:flex;"><span>This is some data to <span style="color:#81a1c1">echo</span>
</span></span></code></pre></div><h4 id="serving-up-my-blog">Serving up my blog</h4>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">Handle</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;/&#34;</span><span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">FileServer</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span><span style="color:#88c0d0">Dir</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;public&#34;</span><span style="color:#eceff4">)))</span>
</span></span></code></pre></div><ul>
<li><strong>Description:</strong> This handler serves static files from the &ldquo;public&rdquo; directory. It&rsquo;s a convenient way to serve HTML, CSS, JavaScript, and other assets.</li>
<li><strong>Testing Rationale:</strong> Allows testing of how the server handles GET requests for files. Can be used to verify that it correctly serves different file types with appropriate headers (Content-Type, Content-Length).</li>
</ul>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/http1.0-from-scratch/blog-screenshot_hu_7b4c6a71338ef462.webp" class="center" width="800px"/>
    


<p>It is quite amazing to see an entire website work just fine with this small amount of code.</p>
<h2 id="beyond-http10">Beyond HTTP/1.0</h2>
<p>While HTTP/1.0 was a significant leap forward, the story doesn&rsquo;t end there. HTTP/1.1, HTTP/2, and HTTP/3 brought further enhancements. In my next article, we&rsquo;ll dive into the world of HTTP/1.1, exploring its advancements over HTTP/1.0; reusable connections, chunked encoding, and TLS will finally enter the scene. From here on out, the focus was less on forming the semantics of HTTP and more on improving performance.</p>
<h2 id="conclusion">Conclusion</h2>
<p>By building this HTTP/1.0 server from the ground up, we&rsquo;ve explored the key components that transformed the web into a more dynamic and interactive space. From the introduction of headers and status codes to the expanded set of HTTP methods, HTTP/1.0 laid the crucial foundation for modern web communication.</p>
<p>While this server is a simplified implementation, it captures the essence of HTTP/1.0 and gives you a hands-on understanding of how the protocol works. As you continue to explore HTTP, you’ll see how later versions, like HTTP/1.1 and HTTP/2, built upon this foundation, optimizing performance and enhancing features.</p>
<p>Remember, the journey of mastering web technologies is continuous. Whether you&rsquo;re diving into newer protocols or digging deeper into foundational ones like HTTP/1.0, each step broadens your understanding and sharpens your skills.</p>
]]></content:encoded></item><item><title>Y'all are sleeping on HTTP/3</title><link>https://kmcd.dev/posts/yall-are-sleeping-on-http3/</link><pubDate>Tue, 06 Aug 2024 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/yall-are-sleeping-on-http3/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/yall-are-sleeping-on-http3/cover_hu_2121eccfa92cb760.webp" /> &lt;/p>
                
                
                </description><content:encoded><![CDATA[<h2 id="wake-up-call">Wake up call</h2>
<p>This post is about HTTP/3 and QUIC. If you don&rsquo;t know what that is, there are <a href="https://www.cloudflare.com/learning/performance/what-is-http3/" rel="external">many</a>, <a href="https://http.dev/3" rel="external">many</a>, <a href="https://datatracker.ietf.org/doc/html/rfc9114" rel="external">many</a>, <a href="https://nordvpn.com/blog/what-is-quic-protocol/" rel="external">many</a>, <a href="https://blog.cloudflare.com/the-road-to-quic" rel="external">many</a>, <a href="https://datatracker.ietf.org/doc/rfc9000/" rel="external">many</a>, <a href="https://www.debugbear.com/blog/http3-quic-protocol-guide" rel="external">many</a> good resources that will get you up to speed. I&rsquo;m writing this post to enlighten people on what has been happening in the last few years.</p>
<p><strong><a href="https://caniuse.com/http3" rel="external">All major browsers</a></strong> support HTTP/3 now.</p>
<p><strong><a href="https://kmcd.dev/posts/yall-are-sleeping-on-http3/#cloud-providers">Most major cloud providers</a></strong> support HTTP/3 now.</p>
<p><strong><a href="https://kmcd.dev/posts/yall-are-sleeping-on-http3/#load-balancers">Most major load balancers</a></strong> support HTTP/3 now.</p>
<p>In a short few years <strong><a href="https://w3techs.com/technologies/details/ce-http3" rel="external">over 30% of web traffic is served with HTTP/3</a></strong>.</p>
<span class="bigtext"><em><strong>HTTP/3 isn&rsquo;t the future. It&rsquo;s the present.</strong></em></span>
<p>Every time I mention HTTP/3 there&rsquo;s always someone who pops up who&rsquo;s completely unaware that it even exists, thinks that it&rsquo;s some minor change or thinks that supporting HTTP/3 is cargo cult behavior. Every web developer should probably know that HTTP/3 exists because HTTP/3 is a giant change. HTTP/3 abandons TCP in favor of a channel-aware UDP-based protocol called QUIC. To me, <em><strong>this feels important!</strong></em> This feels like people need to be talking about it, doing more experiments around QUIC, and writing more tooling, security analysis and benchmarks. QUIC has the potential to dethrone TCP as the reliable layer 4 protocol. This is a big deal.</p>
<p>Why should you care about HTTP/3 if you&rsquo;re not a big nerd who likes learning about network protocols? Because it promises to bring a lot of advantages: faster page loads, smoother video streaming, and more resilient connections. Plus, I think web developers need to know about the foundations of their profession. And this foundation has been changing. Rapidly.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/yall-are-sleeping-on-http3/thinking_hu_f9f91c37317c12af.webp" class="center" width="600px"/>
    


<h2 id="whats-wrong-with-tcp">What&rsquo;s wrong with TCP?</h2>
<h3 id="multiple-streams-one-connection">Multiple streams: One Connection</h3>
<p>There are issues with multiplexing multiple streams onto one TCP-backed connection. This is because TCP treats all data on a connection as one big stream. If one part of that stream gets delayed or lost, everything else gets stuck behind it, like a traffic jam on a single-lane road. This is called &lsquo;<a href="https://http3-explained.haxx.se/en/why-quic/why-tcphol" rel="external">head-of-line blocking</a>,&rsquo; and it can significantly slow down web page loading, especially when multiple resources (like images, scripts, and stylesheets) are being requested simultaneously.</p>
<h3 id="it-isnt-good-in-dynamic-network-environments">It isn&rsquo;t good in dynamic network environments</h3>
<p>As mentioned in the last section, TCP connections are slow to set up because of the number of round trips required. In environments that change often, causing your web clients to switch networks (and client IP addresses), the clients will have to re-establish an entirely new connection each time you switch networks. Think about the large pause that happens when you switch wifi networks while using video chat. That&rsquo;s exactly this issue. TCP was not designed to handle this situation without that pause. In the following section, I&rsquo;m going to tell you how QUIC (and HTTP/3) handles this situation in a much more reliable and smooth way. This is extremely relevant today in a world where mobile phones can choose to swap between multiple wifi and mobile networks.</p>
<h3 id="why-cant-i-use-wifi-and-mobile-at-the-same-time">Why can’t I use wifi and mobile at the same time?</h3>
<p>This <em>is</em> actually possible with TCP, using a feature called <a href="https://www.multipath-tcp.org/" rel="external">multipath TCP</a>, but the rollout has been slow and difficult for TCP. QUIC&rsquo;s built-in support for connection migration and 0-RTT resumption offers a smoother and more efficient solution to this problem, potentially enabling true multipath connectivity in the future.</p>
<h2 id="enter-quic-and-http3">Enter: QUIC and HTTP/3</h2>
<h3 id="faster-connections">Faster Connections</h3>
<p>First off, QUIC requires far fewer round trips to set up an encrypted connection. Instead of 3 needed for TLS+HTTP/2, QUIC just has one.</p>
<h3 id="zero-round-trip-time-0-rtt-resumption">Zero Round Trip Time (0-RTT) Resumption</h3>
<p><a href="https://blog.apnic.net/2023/10/02/how-quic-helps-you-seamlessly-connect-to-different-networks/" rel="external">QUIC connections can be resumed</a>, even if the client&rsquo;s IP address changes. This allows connections to be re-established with <em><strong>zero</strong></em> round trips, so when you are on a conference call and switch wifi networks your HTTP/3 connection can resume instantaneously. While there have been security concerns around this feature, such as the potential for replay attacks, these have been largely mitigated through measures like rotating keys and client IDs.</p>
<p>I mentioned earlier how the rollout of multipath for TCP hasn&rsquo;t been super successful. With the features to support 0-RTT, support for multipath is essentially built-in, so you may see more concurrent usage of wifi, ethernet and mobile networks in the future.</p>
<h3 id="multiplexing">Multiplexing</h3>
<p>In contrast to TCP+HTTP/2, QUIC ensures that packet loss or delays on one stream don&rsquo;t impact others. This is because QUIC manages ordering guarantees at the individual stream level, not for the entire connection, effectively <a href="https://http.dev/3#the-quic-protocol-and-limitations-with-tcp" rel="external">eliminating head-of-line blocking</a>.</p>
<h3 id="improved-congestion-control">Improved Congestion Control</h3>
<p>QUIC&rsquo;s <a href="https://www.catchpoint.com/http2-vs-http3/quic-vs-tcp" rel="external">more responsive congestion control</a> leads to faster recovery from packet loss.</p>
<h3 id="but-i-heard-udp-is-unreliable">&ldquo;But I heard UDP is unreliable&rdquo;</h3>
<p>It&rsquo;s a common misconception that UDP is inherently unreliable compared to TCP. TCP packets are just as unreliable, but they transparently get retried by TCP client and server implementations. In other words, both TCP and UDP packets can be lost or corrupted during transmission; the difference lies in how they handle those errors. While it is true that UDP doesn&rsquo;t offer the same built-in guarantees as TCP, QUIC implements the same guarantees on top of UDP.</p>
<p>Think of it like this: TCP is like a delivery service that meticulously tracks every package and resends any that go missing. UDP, on the other hand, is more like a bulk mailer, sending out a flood of letters and hoping most of them arrive. However, QUIC builds its own tracking system on top of UDP, ensuring reliable delivery of data.</p>
<p>This means QUIC can enjoy UDP&rsquo;s speed and flexibility without sacrificing reliability. In fact, in some cases, QUIC can even outperform TCP in terms of packet loss recovery, thanks to its advanced congestion control mechanisms.</p>
<p>So, while the &ldquo;UDP is unreliable&rdquo; mantra might have been true in the past, protocols built on UDP can be even more reliable than TCP.</p>
<h2 id="lets-see-how-far-weve-come">Let&rsquo;s see how far we&rsquo;ve come</h2>
<p>Okay, so I mentioned how available HTTP/3 is a few times now. But let&rsquo;s look at the specifics: browser support, cloud support and support when self-hosting.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/yall-are-sleeping-on-http3/tech-elite_hu_144bed629f3bcdd8.webp" class="center" width="600px"/>
    


<h3 id="web-browsers">Web Browsers</h3>
<p>Let&rsquo;s start with web browsers. HTTP/3 support doesn&rsquo;t matter if browsers don&rsquo;t support the technology. <a href="https://caniuse.com/http3" rel="external">CanIUse</a> shows the support for each browser and it looks very good for all major browsers:</p>
<ul>
<li><a href="https://www.chromium.org/quic/" rel="external">Chrome</a></li>
<li><a href="https://hacks.mozilla.org/2021/04/quic-and-http-3-support-now-in-firefox-nightly-and-beta/" rel="external">Firefox</a></li>
<li><a href="https://techcommunity.microsoft.com/t5/discussions/how-to-enable-http3/m-p/2265230" rel="external">Edge</a></li>
<li>Opera</li>
</ul>
<p>The only caveat is that Safari only enables HTTP/3 for a portion of users, but Apple will come around eventually.</p>
<h3 id="cloud-providers">Cloud Providers</h3>
<p>Next, if you want to host websites with HTTP/3 it would be awesome if cloud providers supported HTTP/3. Well, you&rsquo;re in luck. Most major cloud providers do support HTTP/3, including:</p>
<ul>
<li><a href="https://developers.cloudflare.com/speed/optimization/protocol/http3/" rel="external">Cloudflare</a></li>
<li><a href="https://cloud.google.com/blog/products/networking/cloud-cdn-and-load-balancing-support-http3" rel="external">Google Cloud CDN and Load Balancer</a></li>
<li><a href="https://aws.amazon.com/about-aws/whats-new/2022/08/amazon-cloudfront-supports-http-3-quic/" rel="external">AWS CloudFront</a></li>
<li><a href="https://techdocs.akamai.com/property-mgr/docs/http3-support" rel="external">Akamai CDN</a></li>
<li><a href="https://techcommunity.microsoft.com/t5/azure-networking-blog/quic-based-http-3-with-application-gateway-feature-information/ba-p/3913972" rel="external">Azure Application Gateway</a> (not widely available as it&rsquo;s in Private Preview)</li>
<li><a href="https://www.cdn77.com/blog/gquic-support-road-to-http3" rel="external">CDN77</a></li>
<li><a href="https://docs.fastly.com/en/guides/enabling-http3-for-fastly-services" rel="external">Fastly CDN</a></li>
</ul>
<h3 id="load-balancers">Load Balancers</h3>
<p>But what if you&rsquo;re setting up your own infrastructure, foregoing the cloud? What are the available options?</p>
<ul>
<li><a href="https://nginx.org/en/docs/quic.html" rel="external">nginx</a></li>
<li><a href="https://gateway.envoyproxy.io/latest/tasks/traffic/http3/" rel="external">Envoy</a></li>
<li><a href="https://caddyserver.com/docs/caddyfile/options#section-global-options" rel="external">Caddy</a></li>
<li><a href="https://docs.litespeedtech.com/lsws/cp/cpanel/quic-http3/" rel="external">LiteSpeed</a></li>
<li><a href="https://h2o.examp1e.net/configure/http3_directives.html" rel="external">H2O</a></li>
<li><a href="https://doc.traefik.io/traefik/routing/entrypoints/#http3" rel="external">traefik</a></li>
<li><a href="https://www.haproxy.com/blog/how-to-enable-quic-load-balancing-on-haproxy" rel="external">haproxy</a></li>
</ul>
<h3 id="is-http2-already-dying">Is HTTP/2 already dying?</h3>
<p>Take a look at the comparative usage of HTTP/2 and HTTP/3 over the last few years:</p>
<p><script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script> Chart.defaults.color = '#fff'; </script><div class="chart">
    <canvas id="23e917feb4763f17"></canvas>
</div>
<script>
    document.addEventListener('DOMContentLoaded', () => {
        var ctx = document.getElementById('23e917feb4763f17')
        var options = 
{
  type: 'line',
  options: {
    plugins: {
        title: {
            display: true,
            text: 'Usage Statistics of HTTP/2 and HTTP/3'
        }
    }
  },
  data: {
    labels: [
        '2016',
        '2017',
        '2018',
        '2019',
        '2020',
        '2021',
        '2022',
        '2023',
        '2024',
        '2024 (Aug)',
    ],
    datasets: [{
      label: 'HTTP/2',
      data: [5.6, 11.2, 23.1, 32.5, 42.6, 49.9, 46.9, 40.0, 35.5, 35.4],
      fill: false,
      borderColor: 'rgb(75, 192, 192)',
      tension: 0.1
    },
    {
      label: 'HTTP/3',
      data: [null, null, null, null, 0, 2.3, 4.1, 24.2, 25.1, 27.8, 30.8],
      fill: false,
      borderColor: 'rgb(255, 99, 132)',
      tension: 0.1
    }]
  }
};
        new Chart(ctx, options);
    });
</script>

<a href="https://w3techs.com/technologies/history_overview/site_element/all/y" rel="external">Source: w3techs.com</a></p>
<p>From this graph, you can see that in a short few years, HTTP/3 usage is rapidly approaching the same usage as HTTP/2. We had &ldquo;peak HTTP/2&rdquo; in 2021 and maybe next year we will see HTTP/3 overtake HTTP/2 to be the de-facto standard for new web deployments.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/yall-are-sleeping-on-http3/reaper_hu_12ef0e077ebc2698.webp" class="center" width="600px"/>
    


<p>I will be honest though, other sources, such as <a href="https://blog.cloudflare.com/http3-usage-one-year-on" rel="external">Cloudflare</a> and <a href="https://pulse.internetsociety.org/technologies" rel="external">Internet Society Pulse</a>, present a slightly different picture, with HTTP/2 still handling a significant portion of traffic.</p>
<h3 id="why-dont-web-developers-know-about-this">Why don&rsquo;t web developers know about this?</h3>
<p>Well, simply put, none of this really intersects with them in any meaningful way. You can&rsquo;t even tell what version of HTTP your browser is making from Javascript. No, <a href="https://developer.mozilla.org/en-US/docs/Web/API/Response" rel="external">not even with the Fetch API</a>. I recently looked into this because I wanted to print out the version of HTTP being used when loading my website in <a href="https://kmcd.dev/http/">a tool that I made</a> but I had to resort to <a href="https://developers.cloudflare.com/ruleset-engine/rules-language/fields/" rel="external">adding headers to the response via Cloudflare</a> to get that information. Because of all of this, I&rsquo;m certain that there&rsquo;s a large number of web developers who have no idea that their website is using HTTP/3.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/yall-are-sleeping-on-http3/the-same_hu_4576cde5d3f874df.webp" class="center" width="400px"/>
    


<p>If you want to know which version of HTTP your browser is using when loading your website, most browser inspector interfaces can show this by going to the network tab, right-clicking the headers and adding &ldquo;Protocol&rdquo; to the list of columns to show. It also usually shows the protocol when you look at the details of each request.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/yall-are-sleeping-on-http3/inspector_hu_30f24d968b42d737.webp" class="center" width="600px"/>
    


<h2 id="challenges-ahead">Challenges Ahead</h2>
<p>There are two main areas to focus on with QUIC: adding more tooling and language support for the protocol.</p>
<h3 id="toolinglanguage-support">Tooling/Language Support</h3>
<p>Even though browsers and load balancers have good support for QUIC, most programming languages don&rsquo;t support HTTP/3 because QUIC presents a vastly different way to communicate. Without kernel support or widely supported bindings, adding QUIC to a language is a bit like re-implementing TCP in the language. TCP is usually relatively easy to add because OS kernels typically implement TCP for you and provide bindings. As far as I know, that isn&rsquo;t the case for QUIC.</p>
<h3 id="will-quic-stay-in-userspace">Will QUIC stay in userspace?</h3>
<p>From what I&rsquo;ve seen, there&rsquo;s no well-supported kernel module for QUIC. There exist some projects that do <a href="https://github.com/lxin/quic" rel="external">add a kernel module for Linux</a> but it doesn&rsquo;t seem like it&rsquo;s heavily used yet. TCP has benefitted a lot from being backed by OS kernels, which gives it performance optimizations not available in user space but that strength is also a weakness. TCP is now hard to change and adapt. It appears like QUIC has evolved much quicker because of its user-space implementations, but should it stay there? <a href="https://www.fastly.com/blog/measuring-quic-vs-tcp-computational-efficiency/" rel="external">Some have reported</a> that it&rsquo;s possible to match the performance of TCP, even when running in userspace. So does QUIC need the performance improvements that are possible in the kernel? Should there be <a href="https://lwn.net/Articles/965134/" rel="external">a hybrid approach</a> that could give a balance of flexibility and performance advantages? To me, those are open questions and I&rsquo;m curious how this will unfold over time.</p>
<h3 id="naysayers">Naysayers</h3>
<p>Since QUIC does present a lot of changes, there are people, <a href="https://www.reddit.com/r/networking/comments/148qz1f/why_is_there_a_general_hostility_to_quic_by/" rel="external">mostly network engineers</a>, who do not like the quick adoption of QUIC. Here are some of the complaints:</p>
<ul>
<li>Most web traffic is a bloated mess anyway, and the additional few milliseconds it takes to stand up a TCP connection doesn&rsquo;t account for a large amount of bloat in modern web applications.</li>
<li>QUIC makes it more difficult for network administrators to monitor and inspect traffic.</li>
<li><a href="https://news.ycombinator.com/item?id=39169357" rel="external">QUIC induces more CPU overhead</a>, as it doesn&rsquo;t have decades of optimization that TCP has.</li>
<li>QUIC is a relatively new protocol, and there may still be undiscovered bugs or vulnerabilities, especially with so many languages/platforms re-implementing the protocol.</li>
</ul>
<p>Further, many argue that the benefits just don&rsquo;t outweigh the risks. I don&rsquo;t think I agree. I think these network engineers are discounting just how much traffic comes from mobile or dynamic (sketchy) wifi environments. However, I am surprised at how quickly and the method by which QUIC has been rolled out. Should we pump the brakes or is it too late?</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/yall-are-sleeping-on-http3/angry-neteng_hu_e971431f9c5d59ec.webp" class="center" width="600px"/>
    


<h2 id="the-future-is-quic">The Future is QUIC</h2>
<p>HTTP/3 and QUIC are more than just incremental improvements; they represent a fundamental shift in how we build the web. By overcoming the limitations of TCP, QUIC offers a faster, more reliable, and more secure foundation for modern Internet communication.</p>
<p>The benefits are clear: faster page loads, smoother video streaming, more responsive applications, and seamless transitions between networks. This translates to a better user experience, increased engagement, and ultimately, a more vibrant and dynamic internet.</p>
<p>But QUIC&rsquo;s potential extends far beyond the web. Its ability to handle real-time communication, coupled with its built-in security features, makes it an attractive option for applications like <a href="https://daposto.medium.com/quic-for-gamenetworking-46cf23936228" rel="external">online gaming</a>, <a href="https://quic.video/blog/replacing-webrtc/" rel="external">video conferencing</a>, and more. There are already some big projects built on QUIC:</p>
<ul>
<li><a href="https://github.com/francoismichel/ssh3" rel="external">SSH3: SSH using QUIC</a></li>
<li><a href="https://github.com/apernet/hysteria" rel="external">Hysteria: Censorship-resistant proxy</a></li>
<li><a href="https://blog.cloudflare.com/getting-cloudflare-tunnels-to-connect-to-the-cloudflare-network-with-quic" rel="external">Cloudflare Tunnels</a></li>
<li><a href="https://quic.video/" rel="external">Media over QUIC</a></li>
<li><a href="https://github.com/grpc/grpc-web/blob/master/doc/roadmap.md" rel="external">WebTransport (based on HTTP/3) was decided on in gRPC-Web</a> to support bidirectional streams</li>
</ul>
<p>While the transition to HTTP/3 is well underway, it&rsquo;s far from complete. There are still challenges to overcome, such as improving tooling, expanding language support, and ensuring compatibility with existing infrastructure. And it remains to be seen if HTTP/3 is ready for mass adoption.</p>
<p>However, the momentum is undeniable. With major browsers, cloud providers, and load balancers embracing QUIC, the future is bright. As developers and engineers continue to innovate and build on this foundation, we can expect to see even more exciting possibilities emerge.</p>
<p>So, what are you waiting for? If you&rsquo;re a web developer or tech enthusiast, now is the time to dive into QUIC and start exploring its potential. Experiment with new applications, build innovative tools and share your findings with the community. I&rsquo;ve started doing this by <a href="https://kmcd.dev/posts/grpc-over-http3/">trying HTTP/3 with gRPC</a>. The future of the internet is being written in QUIC, and you can be a part of it.</p>
<p>If you want to learn more, here are a lot of resources that I used to research for this article:</p>
<ul>
<li><a href="https://datatracker.ietf.org/doc/html/rfc9114" rel="external">HTTP/3 Spec (RFC 9114)</a></li>
<li><a href="https://datatracker.ietf.org/doc/rfc9000/" rel="external">QUIC Spec (RFC 9000)</a></li>
<li><a href="https://www.fastly.com/blog/measuring-quic-vs-tcp-computational-efficiency/" rel="external">QUIC matches TCP&rsquo;s efficiency, says our research. | Fastly</a></li>
<li><a href="https://github.com/lxin/quic" rel="external">QUIC in Linux Kernel</a></li>
<li><a href="https://www.chromium.org/quic/" rel="external">Chromium&rsquo;s document on QUIC</a></li>
<li><a href="https://codilime.com/blog/http3-protocol/" rel="external">HTTP/3 Protocol — Performance and Security Implications</a></li>
<li><a href="https://http3-explained.haxx.se/en" rel="external">HTTP/3 explained</a></li>
<li><a href="https://caniuse.com/http3" rel="external">Can I Use HTTP/3</a></li>
<li><a href="https://sanjeev41924.medium.com/http-3-challenges-to-security-and-possible-response-e81f429506e0" rel="external">HTTP/3 — Challenges to security and possible response</a></li>
<li><a href="https://pulse.internetsociety.org/blog/the-challenges-ahead-for-http-3" rel="external">The Challenges Ahead for HTTP/3</a></li>
<li><a href="https://medium.com/tech-internals-conf/http-3-shiny-new-thing-or-more-issues-6e4fe14e52ea" rel="external">HTTP/3: Shiny New Thing, or More Issues?</a></li>
<li><a href="https://w3techs.com/technologies/history_overview/site_element/all/y" rel="external">W3 Techs - Historical yearly trends in the usage statistics of site elements for websites</a></li>
<li><a href="https://blog.apnic.net/2023/09/25/why-http-3-is-eating-the-world/" rel="external">Why HTTP/3 is eating the world</a></li>
<li><a href="https://blog.apnic.net/2023/10/02/how-quic-helps-you-seamlessly-connect-to-different-networks/" rel="external">How QUIC helps you seamlessly connect to different networks</a></li>
<li><a href="https://lwn.net/Articles/965134/" rel="external">net: In-kernel QUIC implementation with Userspace handshake</a></li>
</ul>
]]></content:encoded></item><item><title>HTTP/0.9 From Scratch</title><link>https://kmcd.dev/posts/http0.9-from-scratch/</link><pubDate>Tue, 30 Jul 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/http0.9-from-scratch/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/http0.9-from-scratch/cover_hu_72f2d6eeec83ba36.webp" /> &lt;/p>
                
                Building the foundation with HTTP/0.9
                </description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Welcome to the first installment of our &ldquo;HTTP from Scratch&rdquo; blog series! In this series, we&rsquo;ll embark on a journey through the evolution of the Hypertext Transfer Protocol (HTTP), the backbone of the World Wide Web. By building simple implementations of each major HTTP version in Go, we&rsquo;ll gain a deep understanding of how this essential protocol has shaped the internet we use every day and how it has evolved to what we have now. <em>Be warned that none of the code will be the most performant, secure or featureful.</em></p>
<p>In this post, we&rsquo;ll travel back to the early days of the web (1991) and explore <a href="https://http.dev/0.9" rel="external">HTTP/0.9</a>, HTTP&rsquo;s initial incarnation. At the time, HTTP/0.9 was a groundbreaking technology that enabled the first web browsers and servers to communicate, laying the foundation for the World Wide Web that we know today. But HTTP/0.9 had its limitations. We&rsquo;ll discuss these shortcomings, which ultimately paved the way for subsequent versions of HTTP that introduced features like headers, status codes, and support for additional HTTP methods, connection reuse, binary framing and, eventually, abandoning TCP for UDP for better reliability and performance. For a more formal description of the HTTP/0.9 specification, you can reference <a href="https://http.dev/0.9" rel="external">http.dev</a> or <a href="https://www.w3.org/Protocols/HTTP/AsImplemented.html" rel="external">w3.org</a>.</p>
<p>To get a hands-on understanding of HTTP/0.9, we&rsquo;ll take a practical approach. Since no modern web servers support this early version, we&rsquo;ll create our own HTTP/0.9 server from scratch using Go. This will allow us to experiment with the protocol and gain valuable insights into its inner workings.</p>
<p>By the end of this post, you&rsquo;ll have a solid grasp of HTTP/0.9 and the foundation it laid for the modern web. You will be well-equipped to continue our journey through the evolution of HTTP in the upcoming articles.</p>
<h2 id="understanding-http09">Understanding HTTP/0.9</h2>
<p>HTTP/0.9, the inaugural version of the Hypertext Transfer Protocol, laid the groundwork for the web as we know it today. It introduced a simple request-response model that facilitated communication between web clients and servers.</p>
<h3 id="the-request">The Request</h3>
<p>In HTTP/0.9, a client sends a single-line request to the server. This request line consists of only two components:</p>
<ol>
<li><strong>GET Method:</strong>  The only method supported in HTTP/0.9. It instructs the server to retrieve the specified resource.</li>
<li><strong>Resource Path:</strong>  The path to the resource on the server that the client wants to access.</li>
</ol>
<p>For example, to request the index.html file from the server&rsquo;s root directory, the client would send:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#bf616a">GET /index.html
</span></span></span></code></pre></div><p>That&rsquo;s&hellip; it. It can&rsquo;t get much simpler than that. Note that there are no headers to indicate content size, compression, content type, etc. This isn&rsquo;t a part of HTTP/0.9 as all of that was introduced in HTTP/1.0.</p>
<h3 id="the-response">The Response</h3>
<p>Upon receiving a request, the server responds with the contents of the requested resource. This response is a simple stream of bytes, usually representing an HTML document. Notably, there are no headers in an HTTP/0.9 response or a status code that tells us if we&rsquo;re receiving an error page or the resource that we asked for. Since there were no status codes, there was no way to indicate if a requested resource was not found — a concept that would later be introduced with the 404 status code.</p>
<h3 id="full-example">Full example</h3>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#bf616a">&gt; GET /index.html
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">&lt; &lt;html&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">&lt; Hello World!
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">&lt; &lt;/html&gt;
</span></span></span></code></pre></div><p>Again, this is an extremely simple protocol at this point. Request a resource and get the data. There&rsquo;s no place to put metadata when requesting or responding. By the way, lines prefixed with <code>&gt;</code> are from the client to the server and <code>&lt;</code> are from the server to the client. Remember this, because this is important for understanding verbose <code>curl</code> output, which we&rsquo;ll use a good amount in the future.</p>
<div class="container">
  <pre class="mermaid">sequenceDiagram
    actor Client

    rect rgb(47,75,124)
        Client ->> Server: TCP SYN
        Server ->> Client: TCP SYN-ACK
        Client ->> Server: TCP ACK
    end

    rect rgb(200,80,96)
        Client ->> Server: HTTP Request
        Server ->> Client: HTTP Response
    end

    rect rgb(47,75,124)
        Server ->> Client: TCP FIN
		Client ->> Server: TCP ACK
    end
  </pre>
</div>
<h3 id="limitations">Limitations</h3>
<p>While revolutionary for its time, HTTP/0.9 had some significant limitations that paved the way for future improvements:</p>
<ul>
<li><strong>No Headers:</strong>  The absence of headers meant that the server could not convey any additional information about the response, such as content type or length.</li>
<li><strong>Only GET:</strong> The only supported method was GET, which meant that clients could only request resources and not submit data to the server.</li>
<li><strong>No Status Codes:</strong>  There was no mechanism to indicate errors or other status information.</li>
</ul>
<h2 id="implementing-an-http09-server">Implementing an HTTP/0.9 Server</h2>
<p>Okay, now let&rsquo;s implement the server.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> Server <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	Addr    <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span>	Handler http<span style="color:#eceff4">.</span>Handler
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>s <span style="color:#81a1c1">*</span>Server<span style="color:#eceff4">)</span> <span style="color:#88c0d0">ListenAndServe</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> s<span style="color:#eceff4">.</span>Handler <span style="color:#81a1c1">==</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1">panic</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;http server started without a handler&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	l<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> net<span style="color:#eceff4">.</span><span style="color:#88c0d0">Listen</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;tcp&#34;</span><span style="color:#eceff4">,</span> s<span style="color:#eceff4">.</span>Addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> err
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> l<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> l<span style="color:#eceff4">.</span><span style="color:#88c0d0">Accept</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatal</span><span style="color:#eceff4">(</span>err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">go</span> s<span style="color:#eceff4">.</span><span style="color:#88c0d0">handleConnection</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>You can see here that our ListenAndServe() listens on the configured TCP port and then loops forever, accepting and handling new connections in separate goroutines. Now let&rsquo;s look at <code>s.handleConnection()</code></p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>s <span style="color:#81a1c1">*</span>Server<span style="color:#eceff4">)</span> <span style="color:#88c0d0">handleConnection</span><span style="color:#eceff4">(</span>conn net<span style="color:#eceff4">.</span>Conn<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	reader <span style="color:#81a1c1">:=</span> bufio<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	line<span style="color:#eceff4">,</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> reader<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadLine</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fields <span style="color:#81a1c1">:=</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fields</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>line<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>fields<span style="color:#eceff4">)</span> <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">2</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	r <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Method<span style="color:#eceff4">:</span>     fields<span style="color:#eceff4">[</span><span style="color:#b48ead">0</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>		URL<span style="color:#eceff4">:</span>        <span style="color:#81a1c1">&amp;</span>url<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">{</span>Scheme<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;http&#34;</span><span style="color:#eceff4">,</span> Path<span style="color:#eceff4">:</span> fields<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">]},</span>
</span></span><span style="display:flex;"><span>		Proto<span style="color:#eceff4">:</span>      <span style="color:#a3be8c">&#34;HTTP/0.9&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		ProtoMajor<span style="color:#eceff4">:</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		ProtoMinor<span style="color:#eceff4">:</span> <span style="color:#b48ead">9</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		RemoteAddr<span style="color:#eceff4">:</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">RemoteAddr</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">String</span><span style="color:#eceff4">(),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	s<span style="color:#eceff4">.</span>Handler<span style="color:#eceff4">.</span><span style="color:#88c0d0">ServeHTTP</span><span style="color:#eceff4">(</span><span style="color:#88c0d0">newWriter</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">),</span> r<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Handling a connection is very simple in HTTP/0.9 because clients can only create a new connection, send a single request and then the connection is closed. This code reads the first line given by the user and then calls <code>strings.Fields</code> to split the different parts of the request up. As a reminder, this is what the request looks like in HTTP/0.9:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#bf616a">GET /path/to/resource
</span></span></span></code></pre></div><p>Okay, now let&rsquo;s look at what <code>newWriter</code> does. ServeHTTP expects a <code>http.ResponseWriter</code>, which looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> ResponseWriter <span style="color:#81a1c1;font-weight:bold">interface</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#88c0d0">Header</span><span style="color:#eceff4">()</span> Header
</span></span><span style="display:flex;"><span>	<span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#88c0d0">WriteHeader</span><span style="color:#eceff4">(</span>statusCode <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Here&rsquo;s what our HTTP/0.9 looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> responseBodyWriter <span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	conn net<span style="color:#eceff4">.</span>Conn
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Header</span><span style="color:#eceff4">()</span> http<span style="color:#eceff4">.</span>Header <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// unsupported with HTTP/0.9</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>b <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> r<span style="color:#eceff4">.</span>conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>r <span style="color:#81a1c1">*</span>responseBodyWriter<span style="color:#eceff4">)</span> <span style="color:#88c0d0">WriteHeader</span><span style="color:#eceff4">(</span>statusCode <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// unsupported with HTTP/0.9</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">newWriter</span><span style="color:#eceff4">(</span>c net<span style="color:#eceff4">.</span>Conn<span style="color:#eceff4">)</span> http<span style="color:#eceff4">.</span>ResponseWriter <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">&amp;</span>responseBodyWriter<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		conn<span style="color:#eceff4">:</span> c<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>The important thing to note here is that HTTP/0.9 doesn&rsquo;t support status codes or headers so we don&rsquo;t need to do anything in <code>Header()</code> and <code>WriteHeader(statusCode)</code></p>
<p>Now let&rsquo;s put it together in a main function:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    addr <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;127.0.0.1:9000&#34;</span>
</span></span><span style="display:flex;"><span>	s <span style="color:#81a1c1">:=</span> Server<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Addr<span style="color:#eceff4">:</span> addr<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		Handler<span style="color:#eceff4">:</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandlerFunc</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			w<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Hello World!&#34;</span><span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Listening on %s&#34;</span><span style="color:#eceff4">,</span> addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> s<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServe</span><span style="color:#eceff4">();</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatal</span><span style="color:#eceff4">(</span>err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Note that the HTTP handler is a normal-looking <code>http.Handler</code> so this server could work with any existing HTTP router or framework.</p>
<p>See the full source at Github: <a href="https://github.com/sudorandom/kmcd.dev/blob/main/content/posts/2024/http0.9-from-scratch/go/server/main.go" target="_blank">main.go</a>.</p>
<h3 id="testing-the-server">Testing the server</h3>
<p>Now we just need to run the server:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go run server/main.go
</span></span><span style="display:flex;"><span>2024/07/27 21:55:28 Listening on 127.0.0.1:9000
</span></span></code></pre></div><p>Now that we have an HTTP/0.9 server, how do we test it?? Since HTTP/0.9 pretty much isn&rsquo;t used anywhere, how do find a client to test this server? Luckily, <code>curl</code> <a href="https://ec.haxx.se/http/versions/http09.html" rel="external">supports HTTP/0.9</a>, so let&rsquo;s try that!</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl --http0.9 http://127.0.0.1:9000/this/is/a/test
</span></span><span style="display:flex;"><span>Hello World!
</span></span></code></pre></div><p>The &ndash;http0.9 flag instructs curl to accept the headerless responses of HTTP/0.9.</p>
<p>You should note that curl is <em>sending</em> a request as if it were HTTP/1.1 but is configured to accept the headerless responses of HTTP/0.9. Here&rsquo;s what the curl manpage says about the flag:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>--http0.9
</span></span><span style="display:flex;"><span>       <span style="color:#81a1c1">(</span>HTTP<span style="color:#81a1c1">)</span> Tells curl to be fine with HTTP version 0.9 response.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>       HTTP/0.9 is a completely headerless response and therefore you can also connect with this to non-HTTP servers and still get a response since curl will simply transparently downgrade - <span style="color:#81a1c1;font-weight:bold">if</span> allowed.
</span></span></code></pre></div><p>I verified this behavior a bit more when I mixed <code>--http1.0</code> and <code>--http0.9</code>. Instead of using <code>HTTP/1.1</code> in the first line of the request, curl declares that it is using <code>HTTP/1.0</code> while still treating the body correctly (not expecting a status code or headers):</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl --http1.0 --http0.9 http://127.0.0.1:9000/this/is/a/test
</span></span><span style="display:flex;"><span>Hello World!
</span></span></code></pre></div><p>By the way, if you don&rsquo;t use the <code>--http0.9</code> (or if your server returns the status code/headers in a format that doesn&rsquo;t make sense) you will receive an error message, &ldquo;Received HTTP/0.9 when not allowed&rdquo;:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl http://127.0.0.1:9000/this/is/a/test
</span></span><span style="display:flex;"><span>curl: <span style="color:#81a1c1">(</span>1<span style="color:#81a1c1">)</span> Received HTTP/0.9 when not allowed
</span></span></code></pre></div><p>You can test this server with simpler tools. For example, <a href="https://en.wikipedia.org/wiki/Netcat" rel="external">netcat</a> (<code>ncat</code>), can be used to make this same request. This will be a theme for the text-based protocols where often it&rsquo;s simpler just to write text out directly to netcat than it is to use other kinds of tooling:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ <span style="color:#81a1c1">echo</span> GET this/is/a/test <span style="color:#eceff4">|</span> ncat 127.0.0.1 <span style="color:#b48ead">9000</span>
</span></span><span style="display:flex;"><span>Hello World!
</span></span></code></pre></div><p>After writing most of this article, I finally realized that I never even attempted to test my web server using a real web browser. It seems like the only major browser that still supports HTTP/0.9 is Firefox, so let&rsquo;s see it!</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/http0.9-from-scratch/firefox_hu_df9d7a76c93b1a9c.webp" class="center" width="600px"/>
    


<p><strong>Yes, it works!</strong> Success!</p>
<h2 id="implementing-an-http09-client">Implementing an HTTP/0.9 Client</h2>
<p>Now that we&rsquo;ve made a server and used existing clients, we might as well make a client in Go. Don&rsquo;t worry, this one is super simple:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>conn<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> net<span style="color:#eceff4">.</span><span style="color:#88c0d0">Dial</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;tcp&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;127.0.0.1:9000&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;err: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">if</span> _<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> conn<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;GET /this/is/a/test\r\n&#34;</span><span style="color:#eceff4">));</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;err: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>body<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadAll</span><span style="color:#eceff4">(</span>conn<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;err: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>body<span style="color:#eceff4">))</span>
</span></span></code></pre></div><p>See the full source at Github: <a href="https://github.com/sudorandom/kmcd.dev/blob/main/content/posts/2024/http0.9-from-scratch/go/client/main.go" target="_blank">main.go</a>.</p>
<p>This code does the following:</p>
<ul>
<li>Establishes a TCP connection to the server.</li>
<li>Sends the HTTP/0.9 request.</li>
<li>Receives the response and displays it to the user.</li>
</ul>
<p>Simple, right? Almost <em><strong>too simple</strong></em>.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#bf616a">$</span> <span style="color:#81a1c1;font-weight:bold">go</span> run client<span style="color:#81a1c1">/</span>main<span style="color:#eceff4">.</span><span style="color:#81a1c1;font-weight:bold">go</span>
</span></span><span style="display:flex;"><span>Hello World<span style="color:#eceff4">!</span>
</span></span></code></pre></div><h2 id="conclusion">Conclusion</h2>
<p>In this post, we delved into the origins of the web by exploring HTTP/0.9. While simple in its design, HTTP/0.9 was a groundbreaking protocol that enabled the first web browsers and servers to communicate.</p>
<p>We explored the basic request-response model of HTTP/0.9, understanding how clients could request resources and servers could deliver them. We also acknowledged the limitations of this early version, including the lack of headers, status codes, and support for other HTTP methods besides GET.</p>
<p>By building a rudimentary HTTP/0.9 server and client in Go, we gained hands-on experience with the protocol&rsquo;s core concepts. We learned how to handle TCP connections, parse requests, and send responses, laying a solid foundation for understanding more advanced HTTP versions.</p>
<p>In the upcoming parts of this series, we&rsquo;ll delve into how HTTP evolved to overcome the limitations of HTTP/0.9 and address the needs of the evolving World Wide Web. We&rsquo;ll explore the introduction of headers, status codes, and additional methods, which enabled more robust and feature-rich communication between web clients and servers. Stay tuned for the next part of our series, where we&rsquo;ll dive into HTTP/1.0 and its significant enhancements over HTTP/0.9. As a sneak peek, these are the major features added to each version:</p>
<ul>
<li>HTTP/0.9: First attempt to transfer generic resources by paths</li>
<li>HTTP/1.0: Adds status codes, headers, verbs</li>
<li>HTTP/1.1: Adds connection re-use so connections can be reused</li>
<li>HTTP/2: Switches from a text-based protocol to binary</li>
<li>HTTP/3: Built on QUIC instead of TCP</li>
</ul>
]]></content:encoded></item><item><title>What version of HTTP are you using?</title><link>https://kmcd.dev/posts/http-tool/</link><pubDate>Tue, 23 Jul 2024 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/http-tool/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/http-tool/cover_hu_ae04e3ed28fd1220.webp" /> &lt;/p>
                
                Find out if your browser using the latest and greatest.
                </description><content:encoded><![CDATA[<p><a href="https://kmcd.dev/http/">Click here to see what version of HTTP your browser is using to load this website</a>.</p>
<h2 id="http3">HTTP/3</h2>
<p>HTTP/3 is the newest version of the protocol for powering the web. It offers the same features as HTTP/1.1 and HTTP/2 but has a drastically different foundation: using a protocol called QUIC that is built on UDP instead of using the good-ol TCP protocol.</p>
<p>Recently I&rsquo;ve been talking <a href="https://kmcd.dev/posts/grpc-over-http3/">about HTTP/3 and how it might be good to use it with gRPC</a>. That&rsquo;s a good idea, but based on many of the comments that I saw in response to that post I feel like many people don&rsquo;t know how much work has been done for HTTP/3 already. The HTTP/3 spec was published by the IETF and has since been adopted by <em>so many companies</em>. Here&rsquo;s just a few:</p>
<ul>
<li>Cloudflare</li>
<li>Google Cloud CDN and Load Balancer</li>
<li>AWS CloudFront</li>
<li>Akamai CDN</li>
<li>Azure CDN</li>
<li>CDN77</li>
<li>Fastly CDN</li>
<li>Azure Application Gateway</li>
<li>nginx</li>
<li>Caddy</li>
<li>HAProxy</li>
<li>and tons more</li>
</ul>
<p>So as you can see, the server side of HTTP/3 is getting more and more ready for prime time. And the browser support is also pretty much there. You can see on <a href="https://caniuse.com/http3" rel="external">caniuse.com</a> that HTTP/3 is supported on all major browsers including all of the familiar names: Chrome, Firefox, Safari, and Edge.</p>
<p>So because web browsers and many web load balancer services already have support for HTTP/3 it shouldn&rsquo;t be surprising to know that <a href="https://w3techs.com/technologies/details/ce-http3" rel="external">30% of websites now support HTTP/3</a>. This is an impressive figure, given how different the foundations of HTTP/3 are compared to HTTP/2 and HTTP/1.1.</p>
<p>As a reminder, here are the benefits of HTTP/3:</p>
<ul>
<li><strong>Faster connection establishment</strong>, which is especially useful with slower, unstable or congested connections.</li>
<li>Completely avoids the <strong>head-of-line blocking</strong> problem, allowing browsers to use a single connection to load everything at full speed.</li>
</ul>
<h2 id="the-tool">The tool</h2>
<p>So, are you curious to know if your browser is leveraging the latest web technology? I built a simple tool that will reveal the HTTP version you&rsquo;re using to access this website.  It&rsquo;ll even let you know if you&rsquo;re enjoying the full benefits of HTTP/3&rsquo;s speed and efficiency. Give it a try and see how your browser stacks up!</p>
<h3 id="how-it-is-made">How It Is Made</h3>
<p>This is a static blog, which obviously makes it a little harder to make a tool like this. In addition to that, the protocol used to connect from the load balancer isn&rsquo;t necessarily the protocol used by clients. In my case, I use Cloudflare as a host and load balancer, so I configured the endpoint to add a new HTTP header that contains the HTTP version used between the client and load balancer. It might look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#bf616a">x-kmcd-http-request-version: HTTP/3
</span></span></span></code></pre></div><h3 id="javascriptbrowser-limitations">Javascript/Browser Limitations</h3>
<p>So, I have this new header being returned, but I need to do something to display the value. One thing that&rsquo;s a little crazy to me is that Javascript on a page <em>cannot read the headers that were returned for the current page.</em> So&hellip; When you load the page, Javascript runs and calls the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" rel="external">Fetch API</a> to retrieve the page again. The response object that is returned <em>does</em> contain the full list of headers, which is what I used to determine the output. By the way, you might ask &ldquo;doesn&rsquo;t the response object also contain the version of HTTP used?&rdquo; and the answer is, unfortunately, no. It seems like it would be simple to add but it&rsquo;s just not there.</p>
<h2 id="check-it-out">Check it out</h2>
<p>So go check out the tool. If you don&rsquo;t get <code>HTTP/3</code> at first you may have to refresh a few times. There are <a href="https://http3-explained.haxx.se/en/h3/h3-altsvc" rel="external">a few different ways</a> to hint to browsers that HTTP/3 is supported and I use all of them&hellip; but many browsers will avoid attempting HTTP/3 for the first few requests to a website until it trusts that HTTP/3 is available. So go on and try it and let me know if it&rsquo;s useful at all:</p>
<p><a href="https://kmcd.dev/http/">Click here to go to the tool.</a></p>
]]></content:encoded></item><item><title>Texans in Denmark</title><link>https://kmcd.dev/posts/texans-in-denmark/</link><pubDate>Tue, 16 Jul 2024 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/texans-in-denmark/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/texans-in-denmark/cover_hu_5f6abe097a46c21.webp" /> &lt;/p>
                
                Why I decided to go from BBQ to Smørrebrød.
                </description><content:encoded><![CDATA[<p>In <a href="https://kmcd.dev/posts/leaving-texas">my last post</a>, I talked about the reasons that me and my family decided to leave Texas. Today I&rsquo;m going to talk about why we decided Denmark as our new home. <strong>Why <em>Denmark</em>?</strong> I get this question often, especially from Danes. I hope to answer how we came up with Denmark as our family&rsquo;s new home and how it has been in the past two and a half years. I do not intend for this to be a guide for others since the details of bureaucracy change quite often and I think everyone&rsquo;s experience will be very different from ours. My colleague has actually written a <a href="https://matduggan.com/developers-guide-to-moving-to-denmark/" rel="external">Developers Guide to Moving to Denmark</a> which is very informative on this front. My of his experiences echo mine. Instead, I want to describe &ldquo;the vibe&rdquo; of the last few years in Denmark, so others might have a general sense of what it might be like to make the same life-changing move.</p>
<h2 id="why-denmark">Why Denmark?</h2>
<p>To give you a better idea of why we chose Denmark, let&rsquo;s dive into the specific factors that influenced our decision.</p>
<h3 id="beautiful-cities-and-landscapes">Beautiful cities and landscapes</h3>
<p>I love the design of many European cities. The historic buildings, the walkable city centers, the metro and train systems, the bus systems that get to most places, etc. So Europe was already very high on our list. However, there are many places to live in Europe, so let&rsquo;s continue.</p>
<h3 id="english-usage">English Usage</h3>
<p>We did realize how hard it would be to live in a country that doesn&rsquo;t have a high proficiency in English, so we naturally gravitated towards places that do use English a good amount. With this criteria, we identified Ireland, England, The Netherlands, Denmark, Sweden and Norway as notable choices.</p>
<h3 id="women-also-work">Women also work</h3>
<p>Denmark has a very good record for women being able to have careers. The gap between the percentage of women and men working is much lower than in most other countries. Women are very much expected to be a part of society, and men are expected to do their share of housework and childcare.</p>
<h3 id="work-life-balance">Work-life Balance</h3>
<p>Denmark has a legal requirement for employees to have five weeks of paid vacation time per year. Many jobs offer a week or two more than that. Non-salary employees also get &ldquo;vacation pay&rdquo; which can be used to keep your pay consistent while you take time off for you and your family. Furthermore, the amount of hours for full-time work in Denmark is 37, which is slightly better than the typical 40. People shouldn&rsquo;t be working their lives away in hopes that one day they will be able to retire and finally do the things that they want to do when their health is likely the worst.</p>
<p>But how does Denmark make this work? How is it possible for so many women to work when there are children to raise? In addition to generous leave policies, Denmark also provides very generous maternity and paternity leave with up to a year off in total. When the child is one year old, subsidized child care starts and is <em>extremely</em> cheap for the quality of care that you get. And from then on, both parents have time to work full-time jobs and continue their careers. It just makes sense. Most citizens here are happy to pay more tax to live in a society where this is how it works. This very much matches my values.</p>
<h3 id="public-health-care">Public health care</h3>
<p>Health care is a touchy subject for everyone, but I firmly believe that making it a public service is the right choice. I&rsquo;m tired of seeing fundraising campaigns to cover basic medical expenses. Your level of care shouldn&rsquo;t be decided on how good of a sob story your relatives can give on GoFundMe. I would rather have a giant fundraiser that everyone, by default, pays into and everyone can tap into if they need. Health care in our new country should be mostly a public service. This is the case for many countries so this criterion didn&rsquo;t actually narrow our decision down very much, but this is a notable difference from The United States where health care is an abomination of private hospitals negotiating with private insurance companies to which you only get access to by being a productive worker at a (usually) private company.</p>
<h3 id="happy-citizens">Happy Citizens</h3>
<p>It feels silly, but the fact that Denmark <a href="https://worldhappiness.report/ed/2024/happiness-of-the-younger-the-older-and-those-in-between/#ranking-of-happiness-2021-2023" rel="external">consistently ranks near the top</a> of the happiest citizens tells me something about the society. Yes, this is likely a flawed study but there seems to be some truth to it. People are generally happy and the poverty rate is low. This matches my values.</p>
<h3 id="weather">Weather</h3>
<p>You may find it surprising that I would list the weather as a positive for Denmark. In contrast to Texas, it&rsquo;s nowhere near as extreme. There are few-to-no tornados, earthquakes or hurricanes. Occasionally there is a storm with strong winds with flooding but it is nowhere near the amount of destruction of tornados. Why do I care about tornados so much? My house was hit by one in 2019. I was alone with my 6-month-old daughter and it was the scariest few minutes of my life that shook up our lives for the next year. I talked more about this in <a href="https://kmcd.dev/posts/leaving-texas">my last post</a>.</p>
<h3 id="biking-culture">Biking culture</h3>
<p>Denmark has a strong bicycle culture. What I like most about Danish cycling culture is that it&rsquo;s not just the super extreme cyclers who have insanely expensive bikes and full-body suits. It&rsquo;s mostly just normal people who use a bike to get to work because it&rsquo;s the cheapest, fastest and usually the most pleasant way to get places.</p>
<h3 id="and-more">And more</h3>
<ul>
<li>Literally Vikings</li>
<li>Our daughter will be bilingual, which is super cool</li>
<li>World leaders in wind energy</li>
<li>Wonderful design aesthetic</li>
<li>Delicious pastries</li>
<li>Low crime rate</li>
<li>Very progressive social policies</li>
<li>Probably a lot more things that I am just forgetting to list</li>
</ul>
<p>All in all, these factors combined painted a picture of Denmark that aligned with our values and aspirations, making it the ideal place for our family to put down roots. So we took a leap of faith and decided to jump into the unknown.</p>
<h2 id="preparing-for-the-move">Preparing for the move</h2>
<p>We decided to <em>NOT</em> do a &ldquo;scouting&rdquo; vacation, so the first time we were to visit Denmark, we were moving there. This seems like a strange decision to most people that we talk to, but I firmly think we did the right thing for us. We reasoned that we weren&rsquo;t going to learn anything from a vacation that would stop us from at least trying to live there for a year. Vacationing is such a different experience than actually living somewhere, and it really is hard to imagine what it is like living somewhere unless you just do it. So we did. The timing of our move was also a crucial factor.</p>
<p>You may ask why we moved when we did. This was the second half of 2021. COVID was still a big deal. Our daughter was two years old at the time, and we considered that it would get harder and harder for her to learn Danish like a native Danish kid the longer we waited. This was the biggest reason we pulled the trigger as fast as we could.</p>
<h3 id="three-months-before-the-move">Three months before the move</h3>
<p>Before our big move, we had a lot of things to settle. We rented an Airbnb for three months so that we could have time to sell almost everything that we owned. Our house. Our cars. And quite literally almost everything else. We took a total of 8 large suitcases, 3 carry-on bags and a stroller (which was destroyed in transit). We packed some clothing, practical items and a few keepsakes. That is all we had to start our new life.</p>
<h4 id="the-job-search">The Job Search</h4>
<p>During these three months, I also interviewed for jobs in Denmark. This could be a post all of its own, but when I decided to start applying to jobs in earnest was July of 2021. One thing I didn&rsquo;t know about Denmark at the time is that the <em>entire month of July</em> is typically when a lot of Danes take off for their summer vacations. This is because most schools have three weeks in July designated for vacations. I wasn&rsquo;t getting a lot of responses simply because the country was quite literally closed for business. But I stuck to it and eventually got traction on a few jobs which eventually ended up in a pretty good offer that I signed. I quite literally had one of my interviews with someone from their summerhouse. After the offer was agreed to, I started the process for my permit. This required that I travel to Houston to officially submit my paperwork and to have my fingerprint scanned and picture taken. Luckily the closest Danish embassy was relatively close.</p>
<p>At this time I was still working at my dream job at Apple. I only left the job so we could expedite the move to Denmark. I did try searching for openings in Denmark within Apple but there really aren&rsquo;t a lot of Apple jobs in Denmark that match my skills. I was interviewing for jobs in The Netherlands as well since it was a very close 2nd place on our short list of places to move to. I did find several opportunities in Amsterdam and I was close to accepting one of them whenever a few people in Denmark decided to check their email while on vacation, thankfully. If things were slightly different, I could be learning Dutch instead of Danish right now.</p>
<h4 id="onward">Onward!</h4>
<p>With a job offer secured, the work permit process underway, and plane tickets booked, it felt like our plans were finally coming together. As we prepared to leave everything familiar behind, we knew that this was just the beginning of our adventure.</p>
<figure><a href="https://kmcd.dev/posts/texans-in-denmark/luggage.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/texans-in-denmark/luggage_hu_c8833fed1cf648cd.png"/>
    </a>
</figure>

<p>The day finally arrived, and we embarked on our journey to Denmark.</p>
<h2 id="arrival-in-denmark">Arrival in Denmark</h2>
<h3 id="day-1">Day 1</h3>
<p>Oddly, the first day was the hardest for me. I can&rsquo;t sleep very well on airplanes so it felt like the longest day on earth. We had two layovers and by the time we got on the third plane, my two-and-a-half-year-old daughter was COMPLETELY over the experience. We were the kind of parents that severely limited our daughter&rsquo;s television-watching time. This has been great so far&hellip; Except when you need to entertain a toddler who isn&rsquo;t interested in TV shows for 14 fucking hours&hellip; then it&rsquo;s not so great. She also was far less interested in the experience of being in an airplane than we expected.</p>
<p>After our plane had finally landed at Copenhagen Airport, we had to get to our Airbnb (we had one rented for a month while we got our rental house settled) to check in and get our keys. Remember that we had several large and heavy suitcases to contend with and this is when we learned that the &ldquo;1st floor&rdquo; is up one and a half flights of stairs because in most of Europe the &ldquo;first floor&rdquo; is one level above the &ldquo;ground floor&rdquo;. We picked the most fun way to discover this. We also were scheduled to get the keys for the rental house soon after we arrived. This schedule was a mistake. Everything took much longer than expected so I was in a rush to get all of our luggage up to the Airbnb in time to get to our appointment. I moved everything we owned up a few flights of stairs before quickly running/walking to the nearby rental house. We ended up making it only a few minutes late. Everything went well with the walkthrough and we headed back to the AirBnB where we slept the first night in our new home country.</p>
<p>I have some pictures of this day but we look absolutely exhausted so I will not be sharing those!</p>
<h3 id="the-first-month">The First Month</h3>
<figure><a href="https://kmcd.dev/posts/texans-in-denmark/bikes.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/texans-in-denmark/bikes_hu_37d6e8044d44a519.png"/>
    </a>
</figure>

<p>On the second day, we bought some bikes, determined to learn how to ride them. Yes, it was pouring rain, so we got the proper Danish welcoming but we didn&rsquo;t let a little rain get in the way of getting things done.</p>
<p>We mostly treated the first few weeks, or so, like a vacation. We walked around and explored our new city like tourists. Then we would come home and build Ikea furniture in our rental house. We ended up only sleeping at the Airbnb for a few days. Once we had a mattress in our rental house there was no way we weren&rsquo;t going to sleep there. We should have realized this but we added a lot of buffer into our plans because the nightmare scenario for us was being stuck without a place to sleep.</p>
<figure><a href="https://kmcd.dev/posts/texans-in-denmark/moving.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/texans-in-denmark/moving_hu_4f3277dd309bde0b.png"/>
    </a>
</figure>

<p>We ended up walking a lot. It&rsquo;s quite insane how much more walking you end up doing whenever most places that you want to go are a walkable distance away or there&rsquo;s transit available that&rsquo;s a walkable distance from your destination. I mentioned cycling culture as a positive for the country, but I did not know how to ride a bicycle at this point. So we ended up learning how to use the bus and metro system. We opted to NOT purchase a car. This ended up being an amazing decision for us because Copenhagen is perfectly navigable without needing a car.</p>
<p>Bureaucracy is a little difficult when you don&rsquo;t speak the language. In Denmark there is a specific order of when you can do certain things&hellip; and some steps took a surprisingly large amount of time, like onboarding to a Danish bank. It was a ridiculous amount of time before I was able to receive my first paycheck (3 months). We luckily planned on much worse things and had plenty of savings to use in the meantime.</p>
<p>As the initial excitement of arrival settled, we began to navigate the realities of daily life in Denmark. The first year was a whirlwind of learning and adjusting.</p>
<h3 id="the-first-year">The first year</h3>
<p>The first year went by so quickly. I was super excited about every little cultural difference. It sounds very strange but grocery shopping took a while to adjust to. Not only was everything in a language we didn&rsquo;t understand (we expected that) but it&rsquo;s strange how much you rely on familiar types of packaging and brands when shopping. It&rsquo;s funny because <em>now</em> I can&rsquo;t even sympathize too much with my past self because I no longer have any issues with grocery shopping. I don&rsquo;t even remember the specifics behind what was so difficult&hellip; But I do remember having a pasta dish using extremely spicy marinara sauce on accident; the dish ended up being inedible.</p>
<figure><a href="https://kmcd.dev/posts/texans-in-denmark/work.jpg" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/texans-in-denmark/work_hu_23b7f10c051c2504.jpg"/>
    </a>
</figure>

<p>I started my new job. We figured out how to sign my daughter up for kindergarten. My wife got a part-time job near home. We rebuilt our wardrobe since we weren&rsquo;t able to take a lot of clothes over with us. I took a stab at learning Danish but I did this with a fully online class and none of the lessons &ldquo;stuck&rdquo; for me, and life ended up getting in the way so I would consider this attempt as a failure.</p>
<p>The weather did take some adjustment. We intellectually knew that the winters would be dark but there&rsquo;s nothing that really prepares for so many dark days. There was a time soon after New Year&rsquo;s when I just wanted it to stop being so dammed dark all the time. But other seasons eventually came. This is when I realized that Denmark may have the perfect spring, summer and autumn weather for me so maybe I can tolerate the dark winters.</p>
<p>Overall, the first year was a period of rapid adaptation and cultural immersion. While we faced challenges, we also embraced new experiences and began to feel a sense of belonging in our adopted home. Throughout the year, we encountered several surprising differences compared to life in Texas.</p>
<h4 id="surprising-differences-that-i-have-noticed-during-the-first-year">Surprising differences that I have noticed during the first year:</h4>
<ul>
<li>Very rare small talk with strangers. To me, it seems like the people who do strike up small talk are not from Denmark.</li>
<li>Americans are loud. I thought of myself as a quiet person but I often felt like I was scream-talking compared to those around me. I have (hopefully) adjusted my speaking volume lower.</li>
<li>Transit etiquette: everyone is extremely quiet.</li>
<li>Copenhagen can be incredibly quiet in general. People talk at low volumes, cars don&rsquo;t go incredibly fast and there are no air conditioning units everywhere that pump out large amounts of background noise. It&rsquo;s often quiet. And pleasant.</li>
<li>No tipping. I don&rsquo;t miss tipping culture.</li>
<li>Fireworks: Danes are absolutely insane about fireworks. They fire them off at random times and places in the city in the weeks leading up to New Year&rsquo;s. It&rsquo;s absolutely crazy and the smoke and noise can make an otherwise quiet night look like a war zone. It all builds up in a crescendo on New Year&rsquo;s evening to what can only be described as the noise of World War III. Dogs don&rsquo;t have a good time in Danish cities during this time.</li>
<li>Feeling of safety; my wife often has to work late nights and has never felt unsafe walking home alone. This is a dramatic difference from where we come from.</li>
<li>Children with cigarettes and walking beer. I will never get used to this. The lack of open carry laws and the drinking age for beer is age 16.</li>
<li>Danes don&rsquo;t seem to like using weed killer to kill grass that sprouts up between paving stones. Instead, you will see old people with a hand-held frame thrower burning off the unwanted plant matter.</li>
<li>No car; I haven&rsquo;t missed having a car. It&rsquo;s so freeing to not have one in a city with many alternative modes of transportation.</li>
<li>Weird traditions: During a holiday called Fastelavn there&rsquo;s a tradition of getting a barrel with a black cat in it and beating the barrel until the cat gets out. They no longer use a cat but the barrel remains, usually filled with candy instead. There&rsquo;s also a Midsummer holiday called Sankt Hans Aften where Danes gather to burn witches. Again, they no longer use real witches but the symbolism is kind of nuts when you first hear about it.</li>
<li>We would come across pikes of cinnamon on the ground close to poles. We asked around to see what the hell was happening here and discovered a fun tradition: If you aren&rsquo;t married by the time you are 25 years old your &ldquo;friends&rdquo; will tie you to a pole and throw cinnamon at you to make you sweeter so you&rsquo;ll have better luck finding a mate. It&rsquo;s, of course, voluntary and all in good fun.</li>
</ul>
<p>Despite these cultural adjustments, the second year brought a sense of belonging and a deeper understanding of Danish life.</p>
<h3 id="the-second-year">The second year</h3>
<p>As we settled into our second year, we began to feel more at home in Denmark. Many aspects of our lives fell into place, giving us a sense of stability and belonging.</p>
<h4 id="permanent-home">Permanent home</h4>
<p>Our lease was running out for our rental house so we decided to look for a more permanent place to stay. We ended up finding an apartment that we loved and we went through the process to purchase it. We&rsquo;ve learned that there are a lot of caveats when purchasing real estate in Denmark as a non-permanent resident: financing is harder to get and you will likely be offered a worse rate unless you put a larger down payment (up to 40%), the Danish government can just say &ldquo;no&rdquo; if you haven&rsquo;t lived in Denmark for more than 5 years, and you can&rsquo;t rent out or sublet the place until you&rsquo;ve lived here for five years. Despite these potential hurdles, the process went surprisingly smoothly for us, thanks in part to the streamlined Danish system and the assistance of a knowledgeable lawyer. We have found that the home-buying process is far more streamlined and easier than the process in the United States. I don&rsquo;t think I physically signed a single thing during the entire process and there were no random $500 charges that popped up during the process.</p>
<figure><a href="https://kmcd.dev/posts/texans-in-denmark/moving2.jpg" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/texans-in-denmark/moving2_hu_e96cc28923a762f4.jpg"
         alt="Obviously, we had another round of Ikea orders to complete our new home."/>
    </a><figcaption>
            <p>Obviously, we had another round of Ikea orders to complete our new home.</p>
        </figcaption>
</figure>

<h4 id="learning-danish">Learning Danish</h4>
<p>I&rsquo;m going to Danish class in person now and it has been working a lot better for me compared to online classes. I&rsquo;m at a weird skill level now where I can understand a good number of basic conversations but it&rsquo;s still challenging for me to form sentences on the fly and all of it is still exhausting. I think this is the point where I can start saturating my mind with Danish shows, newspapers, magazines, books and music so my brain starts thinking in Danish more naturally. But it&rsquo;s going well. My daughter is 5 years old now and is speaking Danish fluently. Our plan of bringing her over early enough to grasp the language like a native speaker has worked exceedingly well.</p>
<h4 id="unions">Unions</h4>
<p>I haven&rsquo;t mentioned <strong>unions</strong> yet. Denmark has unions for most industries and several general-purpose unions. They appear to operate a bit differently than what we see in the US but the principal is the same. Unions negotiate with employers for better working conditions and pay. They will also review employment contracts for you and if there is ever a disagreement between you and your employer you can get legal assistance from them. This system appears to work well in Denmark. There&rsquo;s no government-enforced minimum wage, but there are often industry-specific minimums that have been agreed upon.</p>
<h4 id="work">Work</h4>
<p>I also switched jobs. After living in Denmark for a year I&rsquo;ve gotten a better grasp of the market and made a move to get closer to the amount I should be getting paid. Part of this was due to data released by my union. As an ex-Apple software engineer from the United States, I took a <strong>deep</strong> pay cut coming to Denmark but it works out well because of our mix of significant savings and our severely downscaled lifestyle. Furthermore, the Danish work culture places a high value on work-life balance and employee well-being, which more than compensates for the lower salary.</p>
<h4 id="cycling">Cycling</h4>
<p>The second year is when I started reliably riding my bike in earnest. As my legs got stronger I transitioned from a heavy e-bike to a normal city bike. It was stolen once (I recovered it thanks to a hidden AirTag). I got tired of carrying around the battery. I cycle 2-3 times a week to work and Danish class. It is still tiring but that is because I can&rsquo;t seem to force myself to go slower than my &ldquo;max speed&rdquo; when cycling. I always seem to want to go at a pace that pushes my limits. I also continued to cycle throughout all of winter, which was a big accomplishment for me; only opting to take the metro if there was ice on the roads or there were particularly windy days. You can see what my commute looked like at the time by checking on the <a href="https://kmcd.dev/posts/morning-copenhagen-commute/">video I made while commuting to work</a>. I&rsquo;ve been intending to make a new video because my office location has changed.</p>
<p><div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/gqjiylaCRuY?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<a href="https://www.youtube.com/watch?v=gqjiylaCRuY" rel="external">Youtube Link (if embed doesn&rsquo;t work)</a></p>
<h4 id="cultural-differences">Cultural Differences</h4>
<p>I listed a bunch of cultural differences in the last section. Well, there&rsquo;s not much to note here because I don&rsquo;t come across them often anymore&hellip; or at least not many of these things seem as strange anymore.</p>
<h2 id="what-now">What now?</h2>
<p>Our family is set on staying here for the long haul. There are some big dates to keep an eye on: passport renewals, visa extensions for me and my family, and applying for permanent residency.</p>
<p>Other than waiting, I have two requirements that I need to actively work on for permanent residency: pass a higher level of Danish and pass an active citizen exam. Then I wait for my 4 years of employment to pass and I can apply. For citizenship, there&rsquo;s a much more difficult exam and I have to wait <em>9 years</em> before I can apply. These requirements can and often do change.</p>
<figure><a href="https://kmcd.dev/posts/texans-in-denmark/nyhavn.jpg" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/texans-in-denmark/nyhavn_hu_e844c997375d9f12.jpg"/>
    </a>
</figure>

<p>As we look ahead, I have a lot of hope that Copenhagen will continue to be an amazing place to live. While the path to permanent residency and eventual citizenship may be long and winding, we are confident in our decision to make Denmark our home. We eagerly await the milestones ahead, embracing the opportunities for growth and the chance to become even more integrated into this vibrant and welcoming society. While we may always be Texans at heart, we are excited to continue to forge a new life in Denmark filled with new experiences, new friendships, and a newfound appreciation for the simple joys of Danish living.</p>
]]></content:encoded></item><item><title>gRPC Over HTTP/3</title><link>https://kmcd.dev/posts/grpc-over-http3/</link><pubDate>Tue, 09 Jul 2024 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/grpc-over-http3/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/grpc-over-http3/cover_hu_6bb864e66717cc70.webp" /> &lt;/p>
                
                Turbocharging gRPC with HTTP/3
                </description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>At the time of writing, HTTP/3 is <a href="https://w3techs.com/technologies/details/ce-http3" rel="external">supported by 30.4% of the top 10 million websites</a>. This market penetration is astounding, but it seems like all of this progress has been possible almost exclusively by work on browsers, load balancers and CDN providers. What about the backend? How&rsquo;s HTTP/3 doing there? The answer, sadly, is not as incredible.</p>
<p>Because of this, I have been very interested in HTTP/3 in the context of gRPC. While gRPC has been instrumental in driving the adoption of HTTP/2, the same isn&rsquo;t true for HTTP/3 even though HTTP/3 promises several benefits that all seem to apply exceptionally well to gRPC services.</p>
<p>In this post, we&rsquo;ll dive into what HTTP/3 is and explore the compelling reasons why it&rsquo;s an ideal fit for gRPC applications. We&rsquo;ll uncover the technical advancements that promise to make HTTP/3 faster, more reliable, and more secure. But we won&rsquo;t stop at theory; we&rsquo;ll get our hands dirty with practical examples in Go, demonstrating how to set up and test gRPC servers and clients over HTTP/3.</p>
<p>By the end of this journey, you&rsquo;ll have a solid understanding of the benefits HTTP/3 brings to gRPC, the tools available to start using it today, and the potential it holds for the future of API development. So, fasten your seatbelts and get ready to experience the next generation of network protocols!</p>
<p>All code examples are available at <a href="https://github.com/sudorandom/example-connect-http3/blob/v0.0.1/" rel="external">sudorandom/example-connect-http3 on GitHub</a>.</p>
<h2 id="why-http3">Why HTTP/3</h2>
<p>gRPC has had a lot of success pushing the world into HTTP/2 but there are some advantages to pushing even further and adopting the new major version of HTTP, <a href="https://http3-explained.haxx.se/en" rel="external">HTTP/3</a>. Let&rsquo;s discuss these advantages before delving into code examples. So why should we use HTTP/3?</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-over-http3/butwhy_hu_77b485a21172adb5.webp" class="center" width="400px"/>
    


<h3 id="faster-connection-establishment">Faster Connection Establishment</h3>
<p>HTTP/3 is built on <a href="https://blog.cloudflare.com/the-road-to-quic" rel="external">QUIC</a> (Quick UDP Internet Connections). You can think of it as a similar protocol to TCP but as the name suggests, it is built on top of UDP. While TCP has historically been the foundation of web communication, offering sequential packet ordering, congestion control and retransmissions, the needs of web browsers have evolved to require some different features and tradeoffs. All three main features of TCP—sequential packet ordering, congestion control, and retransmissions—can sometimes hinder the performance of modern web applications. Acknowledging these trade-offs led to the development of QUIC and HTTP/3, which both leverage UDP.</p>
<p>As the internet evolved better security practices, we&rsquo;ve layered TLS on top of TCP, so a TCP connection gets established first and then an HTTP/gRPC/etc. request can be made. This layering made it much easier to slowly adopt TLS but it has also made it slower to establish connections that need TLS because it added more round trips between the server and client before a connection can be &ldquo;established&rdquo;. Each round trip introduces latency, delaying the time it takes for a request to reach the server and receive a response. Here&rsquo;s what it looks like with HTTP/1.1 and HTTP/2:</p>
<div class="container">
  <pre class="mermaid">sequenceDiagram
    actor Client

    rect rgb(47,75,124)
        Client ->> Server: TCP SYN
        Server ->> Client: TCP SYN-ACK
        Client ->> Server: TCP ACK
    end

    rect rgb(102,81,145)
        Client ->> Server: TLS Client Hello
        Server ->> Client: TLS Server Hello
        Client ->> Server: TLS Server Finished
        Server ->> Client: TLS Client Finished
    end

    rect rgb(200,80,96)
        Client ->> Server: HTTP Request
        Server ->> Client: HTTP Response
    end
  </pre>
</div>
<p>Yes, this process involves <strong>three round trips</strong> before the client even sends a request. Viewed in this way, this seems shockingly slow to me. By combining TLS 1.3, QUIC and HTTP/3, we can do away with several of those round trips. So with HTTP/3 it typically looks like this with only a single round trip before we can issue our request:</p>
<div class="container">
  <pre class="mermaid">sequenceDiagram
	    actor Client

    rect rgb(47,75,124)
        Client ->> Server: QUIC
        Server ->> Client: QUIC
        Client ->> Server: QUIC
    end

    rect rgb(200,80,96)
        Client ->> Server: HTTP Request
        Server ->> Client: HTTP Response
    end
  </pre>
</div>
<p>But wait, we can actually go further. With a feature called <code>0-RTT</code>, the number of round trips for a new connection can actually be <strong>ZERO</strong>. The idea is that the client can presume that a connection will be established and send the request based on that presumption. 0-RTT quite literally removes the need to do a round trip before making the first request. It looks like this:</p>
<div class="container">
  <pre class="mermaid">sequenceDiagram
    actor Client

    rect rgb(47,75,124)
        Client ->> Server: QUIC
    end
    rect rgb(200,80,96)
        Client ->> Server: HTTP Request
    end

    rect rgb(47,75,124)
        Server ->> Client: QUIC
    end
    rect rgb(200,80,96)
        Server ->> Client: HTTP Response
    end
  </pre>
</div>
<p>Note that 0-RTT requires the client to have some cached information about the server so it&rsquo;s not always possible to use. Also, there are still some security concerns, mostly focused on the potential for denial of service attacks.</p>
<h3 id="head-of-line-blocking">Head-of-line Blocking</h3>
<p>HTTP/2 has allowed gRPC to be quite good at multiplexing multiple requests and streams onto a single connection. This wasn&rsquo;t possible with HTTP/1.1. However, there is an issue that can arise due to TCP&rsquo;s guarantee of delivering packets <strong>in order</strong> even if they arrive out of order. This is an issue because packets for a request could be waiting for retransmissions from another request that is using the same connection. From gRPC&rsquo;s point of view, these requests are independent things, so there&rsquo;s no need to wait but TCP does not know about these separate streams, so we end up experiencing a so-called <a href="https://en.wikipedia.org/wiki/Head-of-line_blocking" rel="external">head-of-line blocking</a> issue.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-over-http3/holb_hu_f5a1f7cbcfe023f4.webp" class="center" width="400px"/>
    


<p>HTTP/3 solves the head-of-line blocking issue by avoiding TCP altogether. Instead, it is built on top of a protocol called QUIC which is built on top of UDP. QUIC is aware of multiple streams so it knows when it is appropriate to deliver packets without having this head-of-line blocking behavior. This makes HTTP/3 much better when dealing with unreliable networks. With its heavy use of streams, gRPC in particular would greatly benefit from the elimination of the head-of-line blocking issue.</p>
<h3 id="encryption-isrequired">Encryption is <em>Required</em></h3>
<p>When creating HTTP/2 there was a lot of disagreement over if it should require TLS. Many have argued that this requirement would hurt the adoption of HTTP/2. Further, they argue that HTTP/2 offers a lot of benefits that can be attained without the use of TLS. As a result, we now have <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-3.2" rel="external">h2c</a>, which is a way to use HTTP/2 without encryption.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-over-http3/encryption_hu_53b737659b43b13f.webp" class="center" width="400px"/>
    


<p>These arguments might have worked for HTTP/2 but they didn&rsquo;t hold up for HTTP/3. HTTP/3 <em>requires</em> encryption and usage of the TLS 1.3 protocol. There are several reasons for this, but the most compelling one is that TLS 1.3 working with QUIC allows for fewer (and sometimes zero) round trips to negotiate a new connection before making a request, which is even faster than HTTP/1.1 without TLS. It&rsquo;s a double win by being both more secure and, theoretically, much faster.</p>
<h2 id="experimentation">Experimentation</h2>
<p>When researching this topic, I discovered that HTTP/3 with gRPC is a bit of a complicated story. I covered this a bit in my <a href="https://kmcd.dev/posts/grpc-the-good-parts/">gRPC: The Good Parts</a> post but the gist is that no decision has been made to officially support HTTP/3 throughout the entire gRPC ecosystem. There is an <a href="https://github.com/grpc/grpc/issues/19126" rel="external">open issue</a> to discuss this and there is an <a href="https://github.com/grpc/proposal/blob/master/G2-http3-protocol.md" rel="external">official proposal</a> also discussing the idea. Some projects are closer to HTTP/3 than others, so here are the implementations that I have found where you can make gRPC over HTTP/3 work:</p>
<ul>
<li><strong>C#</strong>: <a href="https://devblogs.microsoft.com/dotnet/http-3-support-in-dotnet-6/#grpc-with-http-3" rel="external">grpc-dotnet</a> is the pioneer here, already containing an implementation of an HTTP/3 transport for gRPC.</li>
<li><strong>Rust</strong>: <a href="https://github.com/hyperium/tonic/issues/339" rel="external">Tonic with the Hyper transport</a> appears to be able to support this, although I&rsquo;m not sure if there are good examples of this in the wild yet.</li>
<li><strong>Go</strong>: <a href="https://github.com/connectrpc/connect-go" rel="external">ConnectRPC</a> for Go uses the standard library http.Handlers, so any http server implementation can be used, including the transport available in <a href="https://github.com/quic-go/quic-go" rel="external">quic-go</a>.</li>
<li><strong>Cronet</strong>: <a href="https://developer.android.com/develop/connectivity/cronet" rel="external">Cronet</a>, designed primarily for mobile clients (Android and iOS), provides a way to utilize Chrome&rsquo;s network stack, including its QUIC and HTTP/3 support. This can be particularly useful for building gRPC clients with HTTP/3 capabilities in mobile environments.</li>
</ul>
<p>If you know about other gRPC implementations that can work with HTTP/3 (client or server), let me know. These are only the ones I know about.</p>
<p>When learning something new I always like getting my hands dirty by using it. I feel like this is the best way to learn. Since Go is my current working language, I decided to explore HTTP/3 using ConnectRPC in Go. I&rsquo;m including the full examples in this article because I&rsquo;ve looked and haven&rsquo;t really been able to find good examples for this. I hope it&rsquo;s helpful for others. The full working examples are on <a href="https://github.com/sudorandom/example-connect-http3/" rel="external">a git repo</a> that I made for this post. Examples will have things like imports omitted for brevity but the full source is linked under each example.</p>
<h3 id="example-server-in-go">Example server in Go</h3>
<p>Let&rsquo;s explore how easy it is to set up a gRPC server with HTTP/3 support using Go. The key players here are:</p>
<ul>
<li><a href="https://github.com/quic-go/quic-go" rel="external">quic-go</a>: This library provides a robust implementation of the QUIC protocol in Go, enabling HTTP/3 communication.</li>
<li><a href="https://github.com/connectrpc/connect-go" rel="external">ConnectRPC</a>: connect-go, the Go implementation of ConnectRPC allows us to define gRPC services using familiar Go HTTP handlers (http.Handler), which greatly simplifies the integration process.</li>
</ul>
<p>I am using the <a href="https://buf.build/connectrpc/eliza" rel="external">Eliza service</a> as the service to implement with ConnectRPC. This is a simple service intended to help demonstrate ConnectRPC. The implementation is un-important, so I omitted it but my implementation just echos back whatever the user sends.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	mux <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServeMux</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Implementation is only in the full source</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">Handle</span><span style="color:#eceff4">(</span>elizav1connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewElizaServiceHandler</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>server<span style="color:#eceff4">{}))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	addr <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;127.0.0.1:6660&#34;</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Starting connectrpc on %s&#34;</span><span style="color:#eceff4">,</span> addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	h3srv <span style="color:#81a1c1">:=</span> http3<span style="color:#eceff4">.</span>Server<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Addr<span style="color:#eceff4">:</span>    addr<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		Handler<span style="color:#eceff4">:</span> mux<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> h3srv<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServeTLS</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;cert.crt&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;cert.key&#34;</span><span style="color:#eceff4">);</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;error: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span></span></span></code></pre></div>
<aside>
<a href="https://github.com/sudorandom/example-connect-http3/blob/v0.0.1/server-single/main.go" target="_blank">See the full source at GitHub.</a>

</aside>

<p>That&rsquo;s it! With this minimal setup, we now have a gRPC server that supports HTTP/3! I just used an <code>http3.Server</code> instance instead of <code>http.Server</code>. <code>http3.Server</code> does have different options related to the differences between QUIC and TCP. Note that it is possible to run an <code>http.Server</code> alongside <code>http3.Server</code> using the same address with the same port. How is this possible? Because HTTP/3 uses UDP and the ports are completely separate from TCP ports. Here&rsquo;s an example:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	mux <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServeMux</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">Handle</span><span style="color:#eceff4">(</span>elizav1connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewElizaServiceHandler</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>server<span style="color:#eceff4">{}))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	addr <span style="color:#81a1c1">:=</span> <span style="color:#a3be8c">&#34;127.0.0.1:6660&#34;</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Starting connectrpc on %s&#34;</span><span style="color:#eceff4">,</span> addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	h3srv <span style="color:#81a1c1">:=</span> http3<span style="color:#eceff4">.</span>Server<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Addr<span style="color:#eceff4">:</span>    addr<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		Handler<span style="color:#eceff4">:</span> mux<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	srv <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span>Server<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Addr<span style="color:#eceff4">:</span>    addr<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		Handler<span style="color:#eceff4">:</span> h2c<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewHandler</span><span style="color:#eceff4">(</span>mux<span style="color:#eceff4">,</span> <span style="color:#81a1c1">&amp;</span>http2<span style="color:#eceff4">.</span>Server<span style="color:#eceff4">{}),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	eg<span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">:=</span> errgroup<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithContext</span><span style="color:#eceff4">(</span>context<span style="color:#eceff4">.</span><span style="color:#88c0d0">Background</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>	eg<span style="color:#eceff4">.</span><span style="color:#88c0d0">Go</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> h3srv<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServeTLS</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;cert.crt&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;cert.key&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>	eg<span style="color:#eceff4">.</span><span style="color:#88c0d0">Go</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">()</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> srv<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServeTLS</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;cert.crt&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;cert.key&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">:=</span> eg<span style="color:#eceff4">.</span><span style="color:#88c0d0">Wait</span><span style="color:#eceff4">();</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;error: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><aside>
<a href="https://github.com/sudorandom/example-connect-http3/blob/v0.0.1/server-multi/main.go" target="_blank">See the full source at GitHub.</a>

</aside>

<p>This code demonstrates running multiple HTTP servers concurrently, providing support for HTTP/1.1, HTTP/2, and HTTP/3 on the same port by leveraging TCP for HTTP/1.1 and HTTP/2 and UDP for HTTP/3.</p>
<p>Oh, and note that this requires a certificate and a key because HTTP/3 <strong>requires</strong> TLS. Here&rsquo;s the command that I used to create a self-signed cert for testing:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>openssl req -new -newkey rsa:4096 -days <span style="color:#b48ead">365</span> -nodes -x509 <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    -subj <span style="color:#a3be8c">&#34;/C=DK/L=Copenhagen/O=kmcd/CN=local.kmcd.dev&#34;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    -keyout cert.key  -out cert.crt
</span></span></code></pre></div><p>That&rsquo;s it! With just a few lines of code, we now have a gRPC server that supports HTTP/3!</p>
<h4 id="testing-with-an-example-client-in-go">Testing with an example client in Go</h4>
<p>Now that we have a server running we need some way to test it, so let&rsquo;s use the standard library <code>http.Client</code> with some quic-go magic sprinkled on top:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">const</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	url        <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;https://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say&#34;</span>
</span></span><span style="display:flex;"><span>	reqBody <span style="color:#eceff4">=</span> <span style="color:#a3be8c">`{&#34;sentence&#34;: &#34;Hello World!&#34;}`</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	roundTripper <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>http3<span style="color:#eceff4">.</span>RoundTripper<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		TLSClientConfig<span style="color:#eceff4">:</span> <span style="color:#81a1c1">&amp;</span>tls<span style="color:#eceff4">.</span>Config<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#616e87;font-style:italic">// we need this because our certificate is self signed</span>
</span></span><span style="display:flex;"><span>			InsecureSkipVerify<span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> roundTripper<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	client <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>http<span style="color:#eceff4">.</span>Client<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Transport<span style="color:#eceff4">:</span> roundTripper<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;connect: &#34;</span><span style="color:#eceff4">,</span> url<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;send: &#34;</span><span style="color:#eceff4">,</span> reqBody<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewRequest</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;POST&#34;</span><span style="color:#eceff4">,</span> url<span style="color:#eceff4">,</span> strings<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewReader</span><span style="color:#eceff4">(</span>reqBody<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;error: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	req<span style="color:#eceff4">.</span>Header<span style="color:#eceff4">.</span><span style="color:#88c0d0">Add</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Content-Type&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;application/json&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	resp<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> client<span style="color:#eceff4">.</span><span style="color:#88c0d0">Do</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;error: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	body<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadAll</span><span style="color:#eceff4">(</span>resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;error: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> resp<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;recv: &#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">string</span><span style="color:#eceff4">(</span>body<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><aside>
<a href="https://github.com/sudorandom/example-connect-http3/blob/v0.0.1/client-http/main.go" target="_blank">See the full source at GitHub.</a>

</aside>

<p>In the case of the client, we only need to define a <code>http3.RoundTripper</code> instance and pass that into a completely normal <code>http.Client</code> instance. That&rsquo;s&hellip; quite literally it. Everything else should be the same.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>2024/07/06 12:57:24 connect:  https://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span>2024/07/06 12:57:24 send:  <span style="color:#81a1c1">{</span><span style="color:#a3be8c">&#34;sentence&#34;</span>: <span style="color:#a3be8c">&#34;Hello World!&#34;</span><span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>2024/07/06 12:57:24 recv:  <span style="color:#81a1c1">{</span><span style="color:#a3be8c">&#34;sentence&#34;</span>:<span style="color:#a3be8c">&#34;Hello World!&#34;</span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>This works as expected! In this next example, I&rsquo;m calling the service using a client built with ConnectRPC, demonstrating how it can work seamlessly over HTTP/3 as well, just by configuring <code>http.Client</code> a little differently.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">const</span> url <span style="color:#eceff4">=</span> <span style="color:#a3be8c">&#34;https://127.0.0.1:6660&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> reqBody <span style="color:#eceff4">=</span> <span style="color:#81a1c1">&amp;</span>elizav1<span style="color:#eceff4">.</span>SayRequest<span style="color:#eceff4">{</span>Sentence<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Hello World!&#34;</span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	roundTripper <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>http3<span style="color:#eceff4">.</span>RoundTripper<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		TLSClientConfig<span style="color:#eceff4">:</span> <span style="color:#81a1c1">&amp;</span>tls<span style="color:#eceff4">.</span>Config<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			InsecureSkipVerify<span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> roundTripper<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	client <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>http<span style="color:#eceff4">.</span>Client<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Transport<span style="color:#eceff4">:</span> roundTripper<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	svcClient <span style="color:#81a1c1">:=</span> elizav1connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewElizaServiceClient</span><span style="color:#eceff4">(</span>client<span style="color:#eceff4">,</span> url<span style="color:#eceff4">,</span> connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithGRPC</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;connect: &#34;</span><span style="color:#eceff4">,</span> url<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;send: &#34;</span><span style="color:#eceff4">,</span> reqBody<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	resp<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> svcClient<span style="color:#eceff4">.</span><span style="color:#88c0d0">Say</span><span style="color:#eceff4">(</span>context<span style="color:#eceff4">.</span><span style="color:#88c0d0">Background</span><span style="color:#eceff4">(),</span> connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewRequest</span><span style="color:#eceff4">(</span>reqBody<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;error: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Println</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;recv: &#34;</span><span style="color:#eceff4">,</span> resp<span style="color:#eceff4">.</span>Msg<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><aside>
<a href="https://github.com/sudorandom/example-connect-http3/blob/v0.0.1/client-connect/main.go" target="_blank">See the full source at GitHub.</a>

</aside>

<p>It works the same, but the output is a bit different since protobuf types print slightly differently from JSON:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>2024/07/06 12:57:12 connect:  https://127.0.0.1:6660
</span></span><span style="display:flex;"><span>2024/07/06 12:57:12 send:  sentence:<span style="color:#a3be8c">&#34;Hello World!&#34;</span>
</span></span><span style="display:flex;"><span>2024/07/06 12:57:14 recv:  sentence:<span style="color:#a3be8c">&#34;Hello World!&#34;</span>
</span></span></code></pre></div><p>Now we have a server and a client that talks HTTP/3! Amazing!</p>
<h4 id="testing-http3-servers-with-curl">Testing HTTP/3 servers with curl</h4>
<p>I wanted to test this with a &ldquo;real&rdquo; HTTP/3 client because maybe there are some weird issues where quic-go servers only work against quic-go clients. To be extra sure, I wanted to validate this further with a well-established HTTP/3 client. So I reached for <code>curl</code>, the de-facto tool for calling HTTP APIs from the command line. So I immediately tried to just run <code>curl --http3</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --json <span style="color:#a3be8c">&#39;{&#34;sentence&#34;: &#34;Hello World!&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --http3 -k -v <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  https://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span>curl: option --http3: the installed libcurl version doesn<span style="color:#a3be8c">&#39;t support this
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">curl: try &#39;</span>curl --help<span style="color:#a3be8c">&#39; or &#39;</span>curl --manual<span style="color:#bf616a">&#39;</span> <span style="color:#81a1c1;font-weight:bold">for</span> more information
</span></span></code></pre></div>
    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-over-http3/suprised_hu_a42315c900e59c3f.webp" class="center" width="400px"/>
    


<p>Wait, what? What&rsquo;s happening?? I thought curl supported HTTP/3! What gives? Well, the story <a href="https://daniel.haxx.se/blog/2024/06/10/http-3-in-curl-mid-2024/" rel="external">isn&rsquo;t that simple</a>. The curl CLI might support HTTP/3 but the libraries that it uses also need to be on the correct version to make it all work. To get around that, I installed curl using <a href="https://blog.cloudflare.com/http3-the-past-present-and-future#using-curl" rel="external">Cloudflare&rsquo;s homebrew formula</a> which will install everything needed to get curl to work with HTTP/3. So let&rsquo;s try it out:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --json <span style="color:#a3be8c">&#39;{&#34;sentence&#34;: &#34;Hello World!&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  --http3 -k -v <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>  https://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span>*   Trying 127.0.0.1:6660...
</span></span><span style="display:flex;"><span>* Server certificate:
</span></span><span style="display:flex;"><span>*  subject: C<span style="color:#81a1c1">=</span>DK<span style="color:#eceff4">;</span> L<span style="color:#81a1c1">=</span>Copenhagen<span style="color:#eceff4">;</span> O<span style="color:#81a1c1">=</span>kmcd<span style="color:#eceff4">;</span> CN<span style="color:#81a1c1">=</span>local.kmcd.dev
</span></span><span style="display:flex;"><span>*  start date: Jul  <span style="color:#b48ead">6</span> 06:14:48 <span style="color:#b48ead">2024</span> GMT
</span></span><span style="display:flex;"><span>*  expire date: Jul  <span style="color:#b48ead">6</span> 06:14:48 <span style="color:#b48ead">2025</span> GMT
</span></span><span style="display:flex;"><span>*  issuer: C<span style="color:#81a1c1">=</span>DK<span style="color:#eceff4">;</span> L<span style="color:#81a1c1">=</span>Copenhagen<span style="color:#eceff4">;</span> O<span style="color:#81a1c1">=</span>kmcd<span style="color:#eceff4">;</span> CN<span style="color:#81a1c1">=</span>local.kmcd.dev
</span></span><span style="display:flex;"><span>*  SSL certificate verify result: self signed certificate <span style="color:#81a1c1">(</span>18<span style="color:#81a1c1">)</span>, continuing anyway.
</span></span><span style="display:flex;"><span>* Connected to 127.0.0.1 <span style="color:#81a1c1">(</span>127.0.0.1<span style="color:#81a1c1">)</span> port <span style="color:#b48ead">6660</span>
</span></span><span style="display:flex;"><span>* using HTTP/3
</span></span><span style="display:flex;"><span>* <span style="color:#81a1c1">[</span>HTTP/3<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>0<span style="color:#81a1c1">]</span> OPENED stream <span style="color:#81a1c1;font-weight:bold">for</span> https://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span>* <span style="color:#81a1c1">[</span>HTTP/3<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>0<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>:method: POST<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>* <span style="color:#81a1c1">[</span>HTTP/3<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>0<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>:scheme: https<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>* <span style="color:#81a1c1">[</span>HTTP/3<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>0<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>:authority: 127.0.0.1:6660<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>* <span style="color:#81a1c1">[</span>HTTP/3<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>0<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>:path: /connectrpc.eliza.v1.ElizaService/Say<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>* <span style="color:#81a1c1">[</span>HTTP/3<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>0<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>user-agent: curl/8.9.0-DEV<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>* <span style="color:#81a1c1">[</span>HTTP/3<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>0<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>content-type: application/json<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>* <span style="color:#81a1c1">[</span>HTTP/3<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>0<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>accept: application/json<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>* <span style="color:#81a1c1">[</span>HTTP/3<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>0<span style="color:#81a1c1">]</span> <span style="color:#81a1c1">[</span>content-length: 28<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>&gt; POST /connectrpc.eliza.v1.ElizaService/Say HTTP/3
</span></span><span style="display:flex;"><span>&gt; Host: 127.0.0.1:6660
</span></span><span style="display:flex;"><span>&gt; User-Agent: curl/8.9.0-DEV
</span></span><span style="display:flex;"><span>&gt; Content-Type: application/json
</span></span><span style="display:flex;"><span>&gt; Accept: application/json
</span></span><span style="display:flex;"><span>&gt; Content-Length: <span style="color:#b48ead">28</span>
</span></span><span style="display:flex;"><span>&gt;
</span></span><span style="display:flex;"><span>* upload completely sent off: <span style="color:#b48ead">28</span> bytes
</span></span><span style="display:flex;"><span>&lt; HTTP/3 <span style="color:#b48ead">200</span>
</span></span><span style="display:flex;"><span>&lt; content-type: application/json
</span></span><span style="display:flex;"><span>&lt; accept-encoding: gzip
</span></span><span style="display:flex;"><span>&lt; date: Sat, <span style="color:#b48ead">06</span> Jul <span style="color:#b48ead">2024</span> 13:17:12 GMT
</span></span><span style="display:flex;"><span>&lt; content-length: <span style="color:#b48ead">27</span>
</span></span><span style="display:flex;"><span>&lt;
</span></span><span style="display:flex;"><span>* Connection <span style="color:#616e87;font-style:italic">#0 to host 127.0.0.1 left intact</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span><span style="color:#a3be8c">&#34;sentence&#34;</span>:<span style="color:#a3be8c">&#34;Hello World!&#34;</span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>Success! You can see that HTTP/3 is being used by inspecting the verbose logging. This flexes our server with HTTP/3 but you may be wondering what this has to do with gRPC because I&rsquo;m only using basic HTTP requests with JSON and you&rsquo;re totally right. The previous examples only leverage one of the three protocols that ConnectRPC provides by default, <a href="https://connectrpc.com/docs/protocol/" rel="external">the connect protocol</a>. Thus far, I haven&rsquo;t validated if the other two protocols, gRPC or gRPC-Web, really work using this transport. To test that, we&rsquo;ll need more gRPC-centric tooling.</p>
<h4 id="adding-http3-to-the-buf-cli">Adding HTTP/3 to the Buf CLI</h4>
<p>I also wanted to test with gRPC-specific tooling to ensure gRPC and gRPC-Web worked as expected. So I added support for HTTP/3 with the buf CLI because it supports calling services using <a href="https://connectrpc.com/docs/protocol/" rel="external">Connect</a>, <a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md" rel="external">gRPC</a> and <a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md" rel="external">gRPC-Web</a>. This ended up being pretty easy and looks similar to the ConnectRPC example client above. I have an <a href="https://github.com/bufbuild/buf/pull/3127" rel="external">open PR here</a> and if you want a sneak peak you can build the Buf CLI from <a href="https://github.com/sudorandom/buf/tree/http3" rel="external">my branch</a>. Below, I show how I tested this new feature with ConnectRPC&rsquo;s demo website:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --http3 <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --schema<span style="color:#81a1c1">=</span>buf.build/connectrpc/eliza <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    -d <span style="color:#a3be8c">&#39;{&#34;sentence&#34;: &#34;Hello world!&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;sentence&#34;</span>: <span style="color:#a3be8c">&#34;Hello...I&#39;m glad you could drop by today.&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>Success! HTTP/3 works now! When I tested this, I was surprised that <a href="https://demo.connectrpc.com" rel="external">demo.connectrpc.com</a> actually supported HTTP/3, but it does! By default, <code>buf curl</code> will use the connect protocol, so we didn&rsquo;t test anything new. So let&rsquo;s try gRPC-Web next:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$buf curl <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --http3 <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --protocol<span style="color:#81a1c1">=</span>grpcweb <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --schema<span style="color:#81a1c1">=</span>buf.build/connectrpc/eliza <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    -d <span style="color:#a3be8c">&#39;{&#34;sentence&#34;: &#34;Hello world!&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;sentence&#34;</span>: <span style="color:#a3be8c">&#34;Hello, how are you feeling today?&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>And now the classic gRPC:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --http3 <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --protocol<span style="color:#81a1c1">=</span>grpc <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --schema<span style="color:#81a1c1">=</span>buf.build/connectrpc/eliza <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    -d <span style="color:#a3be8c">&#39;{&#34;sentence&#34;: &#34;Hello world!&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#a3be8c">&#34;code&#34;</span>: <span style="color:#a3be8c">&#34;internal&#34;</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#a3be8c">&#34;message&#34;</span>: <span style="color:#a3be8c">&#34;protocol error: no Grpc-Status trailer: unexpected EOF&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>Wait, <em><strong>what</strong></em>? Why is it complaining about missing HTTP trailers? What gives?</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-over-http3/trailers_hu_41da2c4f990cf317.webp" class="center" width="400px"/>
    


<p>HTTP trailers are a recurring issue for gRPC. HTTP trailers are a special type of HTTP header that is sent at the very end of a message body after all the data has been transmitted. They are useful for sending metadata that cannot be determined until the entire message is known, such as checksums, signatures, or other end-of-message signals. gRPC relies on trailers to return status codes and error details.</p>
<p>To explain why I am not receiving the HTTP trailers, I had to dig into <a href="https://github.com/quic-go/quic-go" rel="external">quic-go&rsquo;s</a> HTTP/3 implementation.</p>
<h4 id="adding-trailer-support-to-quic-go">Adding trailer support to quic-go</h4>
<p>When I looked into quic-go, I discovered that it <em>doesn&rsquo;t support <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Trailer" rel="external">HTTP trailers</a> yet</em>. There is <a href="https://github.com/quic-go/quic-go/issues/2266" rel="external">an issue</a> and a <a href="https://github.com/quic-go/quic-go/pull/2344" rel="external">related pull request</a>. But the issue is four years old, the PR is two years old, and both are still open after all of this time. This seemed quite crazy to me at first, but, on reflection, I realized that gRPC is the most popular thing that currently uses HTTP trailers. However, <code>grpc-go</code> directly uses HTTP/2 support from <code>golang.org/x/net/http2</code> instead of using <code>net/http</code> so it would require a good amount of work to get HTTP/3 support through quic-go. Therefore, this issue probably isn&rsquo;t on the radar of anyone working on <code>grpc-go</code>. However, it is trivial (as seen above) to use quic-go with ConnectRPC, so I think this is a unique situation where progress can be made quickly.</p>
<p>So I ended up implementing trailer support for clients and I <a href="https://github.com/quic-go/quic-go/issues/2266" rel="external">submitted my own PR</a>. I don&rsquo;t think it is quite ready yet but when I use <a href="https://github.com/sudorandom/quic-go/tree/client-trailers" rel="external">my branch of quic-go</a> with <a href="https://github.com/sudorandom/buf/tree/http3" rel="external">my branch of the buf CLI</a>, gRPC actually works with HTTP/3!</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --http3 <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --protocol<span style="color:#81a1c1">=</span>grpc <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --schema<span style="color:#81a1c1">=</span>buf.build/connectrpc/eliza <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    -d <span style="color:#a3be8c">&#39;{&#34;sentence&#34;: &#34;Hello world!&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;sentence&#34;</span>: <span style="color:#a3be8c">&#34;Hello there...how are you today?&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>There are a few issues that I have with my PR. I based it off of the earlier PR but many things have changed with the codebase that actually make it harder to implement this feature. However, it&rsquo;s super encouraging that this code seems to work without too much fuss.</p>
<h3 id="experiment-results">Experiment Results</h3>
<p>My experimentation shows that while Go doesn&rsquo;t yet have full, native support for gRPC over HTTP/3, there are practical workarounds available today. Both the gRPC-Web and Connect protocols function seamlessly over HTTP/3, and in fact, ConnectRPC may already be leveraging HTTP/3 in production environments where infrastructure allows. I discovered this firsthand with the <a href="https://connectrpc.com/demo/" rel="external">ConnectRPC demo website</a>, which will connect using HTTP/3 from the browser to the load balancer (after a few requests so the browser knows HTTP/3 is available).</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-over-http3/demo-connectrpc_hu_ad658e51d3dd4240.webp" class="center" width="400px"/>
    


<aside>
Note: While the connection between my browser and the demo service is HTTP/3, it is likely that HTTP/2 is being used between the load balancer and the backend service. Even without HTTP/3 through the entire stack, the reduced number of round trips between the load balancer and the end user is likely still an advantage due to the decreased connection latency.

</aside>

<h2 id="conclusion">Conclusion</h2>
<p>In this post, we&rsquo;ve explored the exciting potential of HTTP/3 to supercharge gRPC performance. We dove into the key advantages of HTTP/3, such as faster connection establishment, elimination of head-of-line blocking, and mandatory encryption. By getting our hands dirty with practical examples in Go, we&rsquo;ve seen firsthand how HTTP/3 can be seamlessly integrated into gRPC services using tools like ConnectRPC and Buf.</p>
<p>While the gRPC ecosystem&rsquo;s full adoption of HTTP/3 is in its early stages, the benefits are clear, and the tools are already available in some libraries and tools. As developers, we have the opportunity to push this technology forward and shape the future of high-performance, secure communication.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-over-http3/i-should-use-http3_hu_b58073cc7505a7e3.webp" class="center" width="400px"/>
    


<p>I encourage you to experiment with HTTP/3 and gRPC in your own projects. Explore different implementations, measure the performance gains, and don&rsquo;t be afraid to dive into the code if you run into issues. Your active engagement with this evolving technology can directly contribute to the ongoing development of gRPC and HTTP/3. Although widespread adoption of HTTP/3 for gRPC on the backend is still in its early stages, if you have the flexibility to control both server and client components, or are working with browser-based clients, you might find compelling use cases for it even today.</p>
<p>I&rsquo;d love to hear about your experiences with HTTP/3 and gRPC. Have you seen significant performance improvements? Or perhaps you&rsquo;ve found that QUIC is slower without the kernel-level optimizations that TCP can take advantage of? What challenges have you encountered while experimenting with this exciting technology? Let&rsquo;s share our experiences and findings because even though HTTP/3 is still finding its footing in this context, there&rsquo;s a lot we can learn from each other.</p>
]]></content:encoded></item><item><title>gRPC: The Good Parts</title><link>https://kmcd.dev/posts/grpc-the-good-parts/</link><pubDate>Tue, 02 Jul 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/grpc-the-good-parts/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/grpc-the-good-parts/cover_hu_208016cb095a54b9.webp" /> &lt;/p>
                
                Not perfect, but still pretty awesome.
                </description><content:encoded><![CDATA[<p>While REST APIs remain a popular choice for building web services, gRPC is increasingly being adopted for its unique advantages in performance, efficiency, and developer experience. You may have seen my post, <a href="https://kmcd.dev/posts/grpc-the-bad-parts/">gRPC: The Bad Parts</a>, where I talk about some of my issues with gRPC. Based on the many comments about that article, I could easily write a sequel with even more complaints. However, today I&rsquo;m going to focus on the <em>good</em> parts of gRPC. It has become obvious to me that many people didn&rsquo;t read the ending of the last post which attempted to outline how many of the points that I made are no longer true. So I figured that I need to give the positive aspects of gRPC a dedicated post.</p>
<p>Let&rsquo;s dive into the key advantages that make gRPC a powerful tool for modern web development.</p>
<h2 id="performance">Performance</h2>
<p>This one might be a little controversial, but <a href="https://protobuf.dev/" rel="external">Protocol Buffers</a> is, indeed, faster than JSON and XML. This continues <a href="https://streamdal.com/blog/ptotobuf-vs-json-for-your-event-driven-architecture/" rel="external">to be demonstrated</a> over and over again. Protobuf is able to be faster for these reasons:</p>
<ul>
<li>Field names are not included in the message. Instead, protobuf uses numbers to distinguish fields. In most cases you&rsquo;ll see field numbers take one or two bytes on the wire when it can be much more than that depending on your JSON field names.</li>
<li>Protobuf&rsquo;s <code>VARINT</code> type allows for small scale integers to take up a single byte, even if it&rsquo;s an int64. Realistically we really don&rsquo;t use that many large numbers so these savings can add up. Again, it&rsquo;s much better than ASCII-encoded numbers being used for each digit.</li>
<li>There&rsquo;s no real winning with strings or byte arrays but compression is still supported with gRPC so at worst this aspect is even with HTTP/JSON.</li>
</ul>
<p>I&rsquo;ve personally seen 50% data transfer savings by switching to protobuf encoding with realistic payloads.</p>
<p>There are <a href="https://reasonablypolymorphic.com/blog/protos-are-wrong/" rel="external">some haters</a> of protobuf encoding, and that&rsquo;s perfectly fine. The only &ldquo;fatal flaw&rdquo; that I&rsquo;ve actually been annoyed with is &ldquo;map values cannot be other maps.&rdquo; It does seem like that should be possible, even when you consider the internal representation of a map:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>map<span style="color:#eceff4">&lt;</span>key_type<span style="color:#eceff4">,</span> value_type<span style="color:#eceff4">&gt;</span> map_field <span style="color:#81a1c1">=</span> N<span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>transforms into:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">MapFieldEntry</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  key_type key <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  value_type value <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">repeated</span> MapFieldEntry map_field <span style="color:#81a1c1">=</span> N<span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>It&rsquo;s super frustrating because I don&rsquo;t understand why <code>value_type</code> can&rsquo;t be a map. The solution to this problem is just to make your own wrapper type to use as the value that contains a map. It is kind of annoying and this does come up semi-often. Crap, this was supposed to be a positive article. Let&rsquo;s get back on track.</p>
<p>I think the protobuf encoding is better than JSON in many ways. However, I understand that sometimes you just want JSON, and with <a href="https://protobuf.dev/programming-guides/proto3/#json" rel="external">gRPC you absolutely can just use JSON</a>. gRPC still has a few binary framing bytes before each message that won&rsquo;t be human readable but if you&rsquo;re really concerned with those check out the <a href="https://kmcd.dev/posts/grpc-the-good-parts/#connectrpc">ConnectRPC</a> section below.</p>
<p>Most gRPC implementations also let you define your own encoding, so it may possible to insert your own favorite encoding if you want to push the limits.</p>
<h2 id="strongly-typed-contracts">Strongly Typed Contracts</h2>
<p>Say goodbye to the guesswork of loosely typed APIs. gRPC&rsquo;s protobuf definitions create rock-solid contracts between client and server. This translates to:</p>
<ul>
<li><strong>Fewer errors:</strong> Clear expectations for data types reduce the chance of mismatched data.</li>
<li><strong>Better code generation:</strong> Automatic generation of client and server code in various languages saves time and effort.</li>
<li><strong>Smoother development cycles:</strong> Consistent contracts make it easier to evolve your API without breaking existing clients.</li>
<li><strong>Generated Documentation:</strong> Automatic generation of documentation means that your documentation will never be out of sync with your API.</li>
</ul>
<p>API contracts are very powerful. For more on this topic, I&rsquo;ve written an article discussing API contracts called <a href="https://kmcd.dev/posts/api-contracts/">Building APIs with Contracts</a>.</p>
<h2 id="streaming-support">Streaming Support</h2>
<p>Streaming support is arguably the best and most unique feature for gRPC. It does away with needing to frequently poll for updates in many scenarios which make it a good candidate for:</p>
<ul>
<li><strong>Chat applications:</strong> Seamlessly handle messages flowing back and forth.</li>
<li><strong>Live updates:</strong> Push updates to clients as soon as they happen.</li>
<li><strong>Any scenario where constant communication is key:</strong> From gaming to financial data, gRPC&rsquo;s streaming capabilities open up a world of possibilities.</li>
</ul>
<p>If you come from the networking world, you might know that gNMI (which is based on gRPC) is the replacement for SNMP. Instead of polling network devices for the same data every minute, you can now use gNMI to subscribe to counters. I&rsquo;ve written more about this in a post called <a href="https://kmcd.dev/posts/gnmi/">Why you should use gNMI over SNMP in 2024</a>.</p>
<h2 id="cross-language-support">Cross-Language Support</h2>
<p>gRPC doesn&rsquo;t care what programming language you prefer. Thanks to code generation tools, you can seamlessly work with gRPC in a wide range of languages, including:</p>
<ul>
<li>Go</li>
<li>Rust</li>
<li>Java</li>
<li>Python</li>
<li>C#</li>
<li>Node.js</li>
<li>Ruby</li>
<li>&hellip;and many more!</li>
</ul>
<p>This promotes flexibility, collaboration, and the ability to choose the right tool for the job. Currently, I believe gRPC has such a large momentum with language support that it&rsquo;s hard for me to consider an alternative that doesn&rsquo;t also speak gRPC. Many alternatives that have similar benefits to gRPC have mediocre support for a handful of languages at best.</p>
<h2 id="pioneered-http2">Pioneered HTTP/2</h2>
<p>gRPC was a driving force behind the adoption of HTTP/2, a major upgrade to the web&rsquo;s underlying protocol. This means you get all the benefits of HTTP/2&rsquo;s:</p>
<ul>
<li><strong>Multiplexing:</strong> Multiple requests and responses can share a single connection, improving efficiency.</li>
<li><strong>Header compression:</strong> Smaller headers mean faster transmission.</li>
<li><strong>Overall performance improvements:</strong> HTTP/2 is simply a faster, more efficient way to communicate over the web.</li>
</ul>
<h3 id="http3">HTTP/3</h3>
<p>There&rsquo;s some movement on HTTP/3 support for gRPC. There is an <a href="https://github.com/grpc/proposal/blob/master/G2-http3-protocol.md" rel="external">open proposal</a> created by the dotnet gRPC library maintainers and there is <a href="https://github.com/grpc/grpc/issues/19126" rel="external">an open issue to discuss actually adding HTTP/3 to the gRPC spec</a>. Frustratingly, there hasn&rsquo;t been a lot of movement on the official gRPC repo to add support directly to any of their implementations, but as you can see from the thread, there&rsquo;s a lot of interest and a lot of people making prototypes that prove the concept.</p>
<p>This is likely an incomplete list but here are the packages that you can likely use HTTP/3 with today:</p>
<ul>
<li>The standard grpc library for C#, dotnet-grpc <a href="https://devblogs.microsoft.com/dotnet/http-3-support-in-dotnet-6/#grpc-with-http-3" rel="external">(ref)</a></li>
<li>It may already be possible in rust with Tonic with the Hyper HTTP transport <a href="https://github.com/hyperium/tonic/issues/339" rel="external">(ref)</a></li>
<li>It&rsquo;s possible in Go if you use <a href="https://connectrpc.com/" rel="external">ConnectRPC</a> with <a href="https://github.com/quic-go/quic-go" rel="external">quic-go</a> - I don&rsquo;t have a link for this, but I&rsquo;ve tested this out myself. This is a topic for a future post!</li>
<li>This is untested but I believe many gRPC-Web implementations in the browser might &ldquo;just work&rdquo; with HTTP/3 as well as long as the browsers are informed of the support via the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Alt-Svc" rel="external">ALT-SVC header</a> and the servers support it.</li>
</ul>
<p>As more servers and clients support HTTP/3 they should see faster connection establishment times, complete removal of the <a href="https://blog.cloudflare.com/the-road-to-quic#headoflineblocking" rel="external">head-of-line blocking problem</a> and much better recovery from packet loss. There&rsquo;s a long way to go here, but there is progress.</p>
<h2 id="bridging-the-gap">Bridging the Gap</h2>
<p>If you&rsquo;re looking to gradually adopt gRPC or need to support existing REST clients, there are several options available <em>today</em>!</p>
<h3 id="jsonhttp-transcoding">JSON/HTTP Transcoding</h3>
<p>Tools like <a href="https://github.com/grpc-ecosystem/grpc-gateway" rel="external">gRPC-Gateway</a>, <a href="https://cloud.google.com/endpoints" rel="external">Google Cloud Endpoints</a> and <a href="https://www.envoyproxy.io/" rel="external">Envoy</a> can expose REST-like interfaces while still reaping the benefits of gRPC on the backend. You can define a service that looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">your</span><span style="color:#81a1c1">.</span>service.v1<span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">option</span> go_package <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;github.com/yourorg/yourprotos/gen/go/your/service/v1&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#a3be8c">&#34;google/api/annotations.proto&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">StringMessage</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> value <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">service</span> YourService <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">rpc</span> Echo<span style="color:#eceff4">(</span>StringMessage<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span> <span style="color:#eceff4">(</span>StringMessage<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">option</span> <span style="color:#eceff4">(</span>google.api.http<span style="color:#eceff4">)</span> <span style="color:#81a1c1">=</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>      post<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#34;/v1/example/echo&#34;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>      body<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#34;*&#34;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#eceff4">};</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>And get a REST-like endpoint where you can make this request:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>curl -XPOST <span style="color:#a3be8c">&#39;{&#34;value&#34;: &#34;my value!&#34;}&#39;</span> http://localhost:8000/v1/example/echo
</span></span></code></pre></div><p>This is pretty amazing because it&rsquo;s doing a lot of the hard work for you and you can now support many different REST APIs without writing any additional code. This is a simple example here but there are many options, like being able to populate message fields from components of the path.</p>
<h3 id="grpc-web">gRPC-Web</h3>
<p>One of the big limitations of gRPC is that it doesn&rsquo;t work on the web with web browsers due to limited support of HTTP trailers. Browsers support receiving trailers but there isn&rsquo;t yet a way to retrieve those trailers from javascript. Yes, this is incredibly frustrating, especially since there are many small use cases where trailer support would be amazing to have.</p>
<p>The gRPC-Web protocol gives browsers the ability to use gRPC, which drastically improves the story of contract-based services in gRPC. It also allows for HTTP/1.1 clients to work with gRPC. Some platforms (I&rsquo;m looking at you, <a href="https://forum.unity.com/threads/support-for-http-2-with-unitywebrequest.1030510/" rel="external">Unity</a>) still don&rsquo;t support HTTP/2, even though it&rsquo;s 2024 and the <code>HTTP/2</code> spec was created nearly a decade ago.</p>
<h3 id="connectrpc">ConnectRPC</h3>
<p><a href="https://connectrpc.com/" rel="external">ConnectRPC</a> automatically generates JSON/HTTP APIs from your gRPC definitions while also maintaining compatibility with gRPC and gRPC-Web. This HTTP protocol, <a href="https://connectrpc.com/docs/protocol/" rel="external">called Connect</a>, follows HTTP standards more closely. For example, the <code>Content-Coding</code> header, <code>Content-Length</code> header, HTTP status codes, etc. all work as expected for unary RPC calls. That means you can run this normal-looking curl command and talk to a gRPC service:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>curl --header <span style="color:#a3be8c">&#34;Content-Type: application/json&#34;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    --data <span style="color:#a3be8c">&#39;{&#34;sentence&#34;: &#34;I feel happy.&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>    https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say
</span></span></code></pre></div><h3 id="twirp">Twirp</h3>
<p><a href="https://twitchtv.github.io/twirp/" rel="external">Twirp</a> is very similar to ConnectRPC. It was developed by Twitch, and is another framework that can help bridge the gap between gRPC and REST. <a href="https://twitchtv.github.io/twirp/docs/spec_v7.html" rel="external">Twirp&rsquo;s approach</a> is to use protobufs to generate an alternative protocol that also aligns more with HTTP conventions. It doesn&rsquo;t also support gRPC and gRPC-Web. Implementing those alongside twirp is left as an exercise for the user if you want to interoperate with other gRPC tooling.</p>
<h2 id="tooling">Tooling</h2>
<p>I have mentioned that gRPC tooling isn&rsquo;t that great. I still agree with that if we&rsquo;re talking about the &ldquo;out of the box&rdquo; tooling from the gRPC project. However, the community is much bigger than the gRPC Authors and someone finally made the protobuf code generation a lot better.</p>
<h3 id="buf-cli">Buf CLI</h3>
<p><a href="https://buf.build/" rel="external">Buf</a> (the company) has made a client called <a href="https://buf.build/product/cli" rel="external">Buf CLI</a>, which I&rsquo;m going to refer to as just &ldquo;buf&rdquo; from here on out.</p>
<p><code>protoc</code> is the official compiler for protobufs, which has plugins for many languages, frameworks, documentation and other kinds of outputs. Buf <em>completely</em> replaces <a href="https://grpc.io/docs/protoc-installation/" rel="external"><code>protoc</code></a> by using the same protoc plugins that <code>protoc</code> uses. How is it better? It adds a set of config files for defining the structure of your protobuf files, including external protobuf dependencies and external plugins using the <a href="https://buf.build/product/bsr" rel="external">Buf Schema Registry</a>. Instead of random makefile directives or bash scripts, we now have a well-defined config file for defining how the protobuf is built, which is amazing.</p>
<p>Similarly, <code>buf curl</code> provides a convenient way to interact with gRPC services, much like the popular tool <a href="https://github.com/fullstorydev/grpcurl" rel="external">grpcurl</a>.</p>
<p>In addition to replacing existing tooling with easier-to-use versions, buf also implements some extremely useful functions. I first started using buf by using <code>buf lint</code>, which helps enforce some <a href="https://buf.build/docs/lint/rules" rel="external">common rules and practices</a> that developers should follow when making their protobuf files. Soon after, I started using <code>buf breaking</code> which will report on breaking changes being made to protobuf files that may break clients. Both have easy-to-use Github actions and were pretty painless to set up.</p>
<p>Adding buf into the mix can greatly improve your workflow with protobufs, especially when working in a larger team or working with other teams.</p>
<h3 id="third-party-protoc-plugins-libraries-and-tools">Third-party protoc plugins, libraries and tools</h3>
<p>There&rsquo;s so many plugins now. I even <a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">made one</a> and to be honest, plugins aren&rsquo;t that hard to make. I think this is the way that API development should work where you base API services off of a contract and generate everything from that same contract. No typos. No confusion over what methods exist. No arguing over REST semantics that has never been clear to anyone.</p>
<ul>
<li><strong><a href="https://github.com/pseudomuto/protoc-gen-doc" rel="external">protoc-gen-doc</a></strong>: Builds gRPC documentation in several formats. The default styling honestly doesn&rsquo;t look the prettiest but it does allow you to specify a custom template which has been amazing for me to generate something custom without requiring an entire plugin.</li>
<li><strong><a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">protoc-gen-connect-openapi</a></strong>: This is my plugin. It generates OpenAPIv3 specs for your ConnectRPC services and has support for <a href="https://github.com/sudorandom/protoc-gen-connect-openapi/blob/main/protovalidate.md" rel="external">protovalidate</a>, <a href="https://github.com/sudorandom/protoc-gen-connect-openapi/blob/main/gnostic.md" rel="external">gnostic OpenAPIv3 annotations</a>, and <a href="https://github.com/sudorandom/protoc-gen-connect-openapi/blob/main/grpcgateway.md" rel="external">gRPC-Gateway annotations</a>.</li>
<li><strong><a href="https://github.com/bufbuild/protovalidate" rel="external">protovalidate</a></strong>: Protovalidate allows you to embed validation rules in your protobuf files which can be used by an associated library to enforce those rules. A large complaint that people have with gRPC is that it&rsquo;s hard to use whenever every single field is optional. Now you can replace a lot of validation code, including required fields, with protobuf options. I&rsquo;m anxiously awaiting <a href="https://github.com/bufbuild/protovalidate/issues/67" rel="external">typescript support</a> so validation logic can be shared on web frontends and backends, which, to me, is the &ldquo;holy grail&rdquo; feature of a contract-driven service.</li>
</ul>
<p>In addition to these libraries and plugins, more tools that you know and love from HTTP are supporting gRPC like <a href="https://blog.postman.com/postman-now-supports-grpc/" rel="external">Postman</a>, <a href="https://docs.insomnia.rest/insomnia/grpc" rel="external">Insomnia</a> and <a href="https://k6.io/docs/using-k6/protocols/grpc/" rel="external">k6</a>.</p>
<p>The availability of numerous third-party plugins underscores the fact that gRPC is more than just a framework – it&rsquo;s a dynamic ecosystem that fosters innovation and empowers developers to customize their workflows to meet their specific requirements.</p>
<h2 id="conclusion">Conclusion</h2>
<p>gRPC offers a compelling set of advantages for modern web development.</p>
<p>Its performance, strong typing, streaming capabilities, cross-language support, and HTTP/2 foundation make it a powerful tool for building efficient and scalable APIs. With various adoption options available, you can gradually incorporate gRPC into your projects and experience its benefits firsthand.</p>
<p>The growing community and active development around gRPC suggest a bright future for this technology. If you&rsquo;re looking to build fast, reliable, and future-proof APIs, gRPC is a tool that deserves a serious look. Dive in, explore the ecosystem, and discover how gRPC can revolutionize the way you approach API development.</p>
]]></content:encoded></item><item><title>Leaving Texas for Greener Pastures</title><link>https://kmcd.dev/posts/leaving-texas/</link><pubDate>Tue, 25 Jun 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/leaving-texas/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/leaving-texas/cover_hu_c36372a20c1b28a0.webp" /> &lt;/p>
                
                Why I left my life behind.
                </description><content:encoded><![CDATA[<p>Today, I&rsquo;m going to talk a bit about a topic that I haven&rsquo;t talked about much on this blog. I have crossed the two-and-a-half-year mark of living in Denmark and I figured it was time to reflect on how it is living in the little Nordic country after living my entire life living around Dallas, Texas. First, though, I want to outline why I decided to leave Texas. In a follow-up post, I will discuss &ldquo;Why Denmark?&rdquo; and reflect on how the last few years have been.</p>
<h2 id="why-leave-texas">Why Leave Texas?</h2>
<p>Most people aren&rsquo;t surprised at why anyone would leave the United States, but I think it&rsquo;s useful to list some of the big reasons. Because I have a terrible memory I will be referencing notes that I made in May 2021. Be warned, many of these notes are political but I just can&rsquo;t avoid political topics because it does have a large influence on my decision to leave.</p>
<h3 id="education">Education</h3>
<p>I don&rsquo;t have a high opinion of public education in Texas. Guess which kinds of schools are WAY less likely to have a school shooting? Private Schools. While I very much had the means to send my child to private school since I had a very high-paying job, the prospect of raising my child in a drastically different environment than the majority of her peers felt extremely gross and classist. And having to pay to feel like your child is relatively safe just seems crazy. It seems broken. This isn&rsquo;t even considering the extremely low resources that many public schools have to do their job of educating children. This doesn&rsquo;t match my values.</p>
<h3 id="health-care">Health Care</h3>
<p>Even for wealthier families, a serious health condition can lead to financial ruin. The United States ties health insurance with your job. So you have to be working to get good health insurance. Many medical conditions can prevent you from working and it&rsquo;s all too common for companies to shove you to the door when your health is declining. Some people might think that the system in the US is fine because Americans don&rsquo;t pay as much taxes because of it. However, the United States spends <em>significantly more</em> than other nations on health care, many of which have public health care. This doesn&rsquo;t match my values.</p>
<h3 id="abortion-and-contraceptives">Abortion and Contraceptives</h3>
<p>Laws against abortion were starting to take shape when we were making our decision. I feel like women and girls deserve health care and autonomy over one of the most impactful processes that happens to their bodies. I was set on leaving Texas for this reason because I have a daughter and I feel strongly that raising a daughter with these kinds of laws is wrong. More and more states have added their own laws against abortion after the overturning of Roe v. Wade and now it looks like it has become a national issue&hellip; This doesn&rsquo;t match my values.</p>
<h3 id="religion">Religion</h3>
<p>I am not religious and I feel like I don’t share the same values and beliefs as most people in Texas or the United States. While there is a growing &ldquo;non-religious&rdquo; demographic in the United States I still feel surrounded by those who don&rsquo;t share my values.</p>
<figure><a href="https://kmcd.dev/posts/leaving-texas/religion.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/leaving-texas/religion_hu_faf44cc512e44dda.png"
         alt="Source: Gallup"/>
    </a><figcaption>
            <p>Source: <a href="https://news.gallup.com/poll/358364/religious-americans.aspx" rel="external">Gallup</a></p>
        </figcaption>
</figure>

<h3 id="work-life-balance">Work-life balance</h3>
<p>Work-life balance is generally awful in the States. Even for high-paying jobs, it is nearly impossible to get the same amount of paid time off as you get by law in many European countries. It is very common to be expected to work way more than is reasonable and work even when you&rsquo;re &ldquo;a little sick&rdquo;. Pregnant women are often expected to work until the day they give birth and return to work soon after birth. And it&rsquo;s absolutely crazy that we solve the wrong problems. For example, many &ldquo;lactation room&rdquo; where working women who have recently been pregnant have a place to pump milk, out of view from others. This outlines so many societal issues for me.</p>
<p>While I do have a high work ethic, life is more than work. The work culture of the United States in general doesn&rsquo;t match my values.</p>
<h3 id="poverty">Poverty</h3>
<p>The United States has a lot of homeless people. There&rsquo;s an adaptation that many people develop when living in a large city in the US where you just stop seeing homeless people. They&rsquo;re still there, but your subconscious mind will actively prevent you from registering that the person is there. This appears to be the only way for people to get through their day. It&rsquo;s sad. This doesn&rsquo;t match my values.</p>
<p>Here&rsquo;s some related data provided by <a href="https://data.oecd.org/inequality/poverty-rate.htm" rel="external">OECD</a>. According to this data, the United States has a poverty rate of 18%. With this data, Denmark is very close to the best with 4.8%.</p>
<figure><a href="https://kmcd.dev/posts/leaving-texas/poverty.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/leaving-texas/poverty_hu_f8d14cfbc12b0a26.png"
         alt="Source: OECD"/>
    </a><figcaption>
            <p>Source: <a href="https://data.oecd.org/inequality/poverty-rate.htm" rel="external">OECD</a></p>
        </figcaption>
</figure>

<h3 id="car-culture">Car Culture</h3>
<p>How most cities are designed in the US is insane. Cars are a required item that everyone has to own. This has a lot of impacts on day-to-day life but there are two I want to focus on here. The age that a kid feels truly autonomous in the United States is age 16 when you can get a driver&rsquo;s license and start driving yourself places. Let&rsquo;s ignore the classism where I assume that every 16-year-old has parents who can afford to buy each of their kids a car. It is also so easy to not think of other cars around you in traffic as people. Everyone is separated by glass and metal. I feel like this subtracts from social cohesion and may be a much more causal part of many other societal ills that the US has.</p>
<p>I am, in fact, a fan of this newer wave of urban planners like <a href="https://www.youtube.com/c/notjustbikes" rel="external">notjustbikes</a> and <a href="https://www.youtube.com/@strongtowns" rel="external">Strong Towns</a>.</p>
<p>Anyway, just listing <a href="https://usa.streetsblog.org/2024/03/05/all-the-ways-that-cars-harm-our-communities-well-almost-all" rel="external">all the ways that car culture harms people</a> can be the topic of several posts. But for now, let&rsquo;s just say that the over-reliance on cars doesn&rsquo;t match my values.</p>
<h3 id="gun-control">Gun control</h3>
<p>Gun ownership is deeply ingrained in American culture. I think that&rsquo;s actually fine. Guns can be fun and in some cases, useful. But having NO control over who gets to access deadly firearms is lunacy. And I don&rsquo;t think Americans can fully understand how off-base Americans are on this until they&rsquo;ve lived in a country with strong gun control. Danish people will randomly set off fireworks. Sometimes in the middle of the day when there&rsquo;s nothing to see. They do it as part of protests and leading up to New Year&rsquo;s you will constantly hear random fireworks being set off all over the city. And then you realize that NO ONE hears these noises and immediately think &ldquo;Active shooter, get to cover!&rdquo; I no longer react to random fireworks this way because I have healed from this trauma that I never knew I had. The gun laws in the United States don&rsquo;t match my values.</p>
<h3 id="weather">Weather</h3>
<p>I hate the Texas heat. It&rsquo;s only bearable due to massive amounts of air conditioning. Due to unstable arctic weather, Texas can also get quite cold, <a href="https://en.wikipedia.org/wiki/2021_Texas_power_crisis" rel="external">causing some notable problems in the state</a>.</p>
<p>That said, I do have one more issue with extreme Texan weather&hellip;</p>
<figure><a href="https://kmcd.dev/posts/leaving-texas/tornado.jpg" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/leaving-texas/tornado_hu_9600327dc655f626.jpg"/>
    </a>
</figure>

<p>My house was <a href="https://en.wikipedia.org/wiki/Tornado_outbreak_of_October_20%E2%80%9322,_2019#North_Dallas%E2%80%93Richardson,_Texas" rel="external">hit by a tornado in 2019</a>. Since my wife was traveling out-of-state visiting relatives at the time I was home alone with our six-month-old daughter. It was a terrifying experience. The house was damaged but was repairable so our family lived out of an apartment for half a year while the house was repaired. It was a hard time for me and I have since experienced extreme anxiety when severe storms happen, which is pretty often in Texas. I was close to seeking a therapist to try to help with anxiety, but, luckily, the reaction has faded in time. I know it&rsquo;s impossible to avoid all kinds of extreme weather but I am done living in tornado alley.</p>
<h3 id="climate-change">Climate Change</h3>
<p>I&rsquo;m not sure how much I need to talk about here. Many Americans just refuse to accept that their lives may have to change to reduce their CO2 emissions. I think it takes a well-informed population to hold companies accountable for their externalities and to make a series of small sacrifices that will end up doing less harm to our environment. I think Americans are actively misinformed in this regard. Some effects of climate change are happening now. It&rsquo;s making <a href="https://www.forbes.com/advisor/homeowners-insurance/why-is-homeowners-insurance-in-florida-such-a-disaster/" rel="external">some areas of the United States impractical to live in already</a> and this is only the beginning. I want to live in a society that cares about the world they live in. Again, it&rsquo;s absolutely insane that I have to say this.</p>
<h3 id="summing-up">Summing Up</h3>
<p>Leaving Texas was a deeply personal decision, driven by a desire for a society that better aligned with my values and aspirations for my family&rsquo;s future. While the Lone Star State holds cherished memories, family and countless opportunities, the challenges I&rsquo;ve outlined ultimately outweighed the benefits.</p>
<p>But amidst the disappointment and frustration, a glimmer of hope emerged – the prospect of a fresh start in a land known for its progressive policies, strong social safety net, and emphasis on well-being.</p>
<p>Next week you can join me in the next installment of this series, where I&rsquo;ll delve into the factors that drew me to Copenhagen, Denmark, and share the joys, struggles, and unexpected lessons learned during my first two years abroad. Will Denmark prove to be the &ldquo;greener pastures&rdquo; I was looking for? Stay tuned to find out.</p>
]]></content:encoded></item><item><title>gRPC: The Bad Parts</title><link>https://kmcd.dev/posts/grpc-the-bad-parts/</link><pubDate>Tue, 18 Jun 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/grpc-the-bad-parts/</guid><description><![CDATA[ 
                <p> <img hspace="5" src="https://kmcd.dev/posts/grpc-the-bad-parts/cover_hu_9a8f03bbeac1c25f.webp" /> </p>
                
                gRPC isn&#39;t perfect but who is?
                ]]></description><content:encoded><![CDATA[<p>gRPC, the high-performance RPC framework, has been super successful (if you work for Google) and has drastically changed the way we all deploy APIs (if you work for Google). gRPC and protobuf is an extremely performant contract-focused framework with extremely wide language support. But it&rsquo;s not without its downsides. Making an RPC framework that requires code generation and support in many programming languages is sure to get some things wrong. As gRPC approaches a decade of usage, it is important to reflect on what could have been better.</p>
<h2 id="learning-curve">Learning Curve</h2>
<p>Let&rsquo;s start out extremely nit-picky. So-called unary RPCs are calls where the client sends a single request to the server and gets a single response back. Why does gRPC have to use such a non-standard term for this that only mathematicians have an intuitive understanding of? I have to explain the term every time I use it. And I&rsquo;m a little tired of it.</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-the-bad-parts/meme_hu_c4668fdfa3b56496.webp" class="center" width="400px"/>
    


<p>Speaking of unary RPCs, the implementation is more complicated than it needs to be. While gRPC&rsquo;s streaming capabilities are powerful, they have introduced complexity for simple RPC calls that don&rsquo;t require streaming. This hurts the ability to inspect gRPC calls because now there is framing on every unary RPC which only makes sense for streaming. Protobuf encoding is complicated enough so let&rsquo;s not add extra gRPC framing where it isn&rsquo;t needed. Also, it doesn&rsquo;t pass my &ldquo;send a friend a cURL example&rdquo; test for any web API. It&rsquo;s just super annoying to explain to someone how to use gRPC. I&rsquo;ve said &ldquo;okay, but is server reflection enabled?&rdquo; so many times. I&rsquo;m just tired of it.</p>
<p>This complexity also bleeds into the tooling with the mandatory code generation step. This can be a hurdle, especially for dynamic languages where runtime flexibility is valued. Additionally, some developers might be hesitant to adopt a technology that necessitates an extra build step. We already need 20 build steps for modern web development, so it&rsquo;s sometimes hard to justify one more.</p>
<h2 id="compatibility-with-the-web">Compatibility with the Web</h2>
<p>The reliance on HTTP/2 initially limited gRPC&rsquo;s reach, as not all platforms and browsers fully supported it. This has improved over time, but it still poses a challenge in some environments. But even with HTTP/2 support, browsers have avoided adding a way to process HTTP trailers so browsers today still cannot use &ldquo;original&rdquo; gRPC. gRPC-Web has acted as a plaster for this issue by avoiding the use of trailers, but it often requires &ldquo;extra stuff&rdquo; like running a proxy that supports gRPC-Web. Which is annoying.</p>
<p>Late Adoption of HTTP/3: The delay in embracing HTTP/3 might have hindered gRPC&rsquo;s ability to take full advantage of the protocol&rsquo;s performance and efficiency benefits. I have personally been affected by the <a href="https://http3-explained.haxx.se/en/why-quic/why-tcphol" rel="external">head-of-line blocking</a> issue that can happen when using gRPC with HTTP/2 and it would be so nice to be able to completely do away with this issue by being able to use HTTP/3 with gRPC. It&rsquo;s strange to see a framework that pushed many languages to support HTTP/2 struggling to do the same thing with HTTP/3.</p>
<h2 id="json-mapping-and-prototext">JSON Mapping and Prototext</h2>
<p>Another area where the &ldquo;timing&rdquo; was wrong was the lack of a standardized JSON mapping early on. It has made gRPC less accessible for developers accustomed to JSON-based APIs and I don&rsquo;t think it ever recovered from that stigma. Having a mapping between protobuf types and JSON simplifies integration and interoperability with existing tools and systems. You would not believe how happy web developers can get when you say &ldquo;yeah, this is a super-efficient binary format&hellip; but you can set this flag and get JSON back if you want to debug.&rdquo; They get unreasonably excited. <em>Unreasonably. excited.</em> Anyway, now that protobuf has standard rules for mapping protobuf types to JSON (and the other way) I feel like the <a href="https://protobuf.dev/reference/protobuf/textformat-spec/" rel="external">protobuf text format</a> is an unnecessary complexity. I don&rsquo;t see a use-case for the text format now that we have JSON. So let&rsquo;s throw the text format away. We don&rsquo;t need it and I&rsquo;m down to pretend like it never existed if everyone else is. Cool?</p>
<h2 id="finite-message-sizes">Finite Message Sizes</h2>
<p>Most Protobuf encoders/decoders expect to fully parse an entire message and give the full response to the consumer but memory is finite and sometimes you might want larger messages. Sometimes you want to stream parts of these larger messages somewhere else and not keep the entire message in memory. Therefore, if you want to, for example, upload large files you&rsquo;re going to need to implement some kind of chunking. While chunking is a reasonable solution for handling large files, the absence of a standardized approach within gRPC might lead to inconsistent implementations and increased development effort.</p>
<p>As a demonstration, here&rsquo;s what it may look like to upload a file with gRPC:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">file_service</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">service</span> FileService <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>   <span style="color:#81a1c1;font-weight:bold">rpc</span> Upload<span style="color:#eceff4">(</span>stream UploadRequest<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span><span style="color:#eceff4">(</span>UploadResponse<span style="color:#eceff4">);</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">UploadRequest</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1">string</span> file_name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1">bytes</span> chunk <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">UploadResponse</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> etag <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>This is both a strength and weakness of protobufs. This concept is super easy to define in protobuf but in practice, the code to properly implement this can be cumbersome and error-prone. And while Google, the creator of gRPC, has figured out solutions for their APIs, the lack of a standardized approach leaves others to reinvent the wheel.</p>
<p>You might be thinking &ldquo;Google uses gRPC in most of their APIs so obviously they&rsquo;ve done this&rdquo; and you&rsquo;d be right. They actually have a gRPC and HTTP version for downloading (potentially large) files. We can compare the <a href="https://github.com/googleapis/google-cloud-go/blob/v0.114.0/storage/grpc_client.go#L996-L1152" rel="external">gRPC</a> and <a href="https://github.com/googleapis/google-cloud-go/blob/v0.114.0/storage/http_client.go#L888-L911" rel="external">HTTP</a> versions directly and gRPC is BY FAR more complex. Go ahead and compare the linked code. I&rsquo;ll wait.</p>
<h2 id="ded-internet-theory">ded internet theory</h2>
<p>I see a lot of gRPC/protobuf communities that are devoid of activity. The lack of visible activity on some websites might create the impression that gRPC is stagnant or less actively maintained. This could deter potential adopters and contribute to slower community growth. This might be a case of too many options, making it difficult to find someone to nerd out about gRPC outside of GitHub issues where such enthusiasm might be perceived as annoying.</p>
<h2 id="bad-tooling">Bad tooling</h2>
<p>For the longest time, when I saw that a codebase uses protobuf I found a weird script that downloads random protobuf files in super custom ways and places them in random paths and then makes a series of super complex calls to <code>protoc</code>. Only google would think not solving dependency management is the solution to dependency management. Google has its own extremely Google-y way of managing dependencies that we peasants can only dream of using.</p>
<h2 id="it-can-be-and-is-better">It can be (and is) better</h2>
<p>While I&rsquo;ve been critical of gRPC, I hope my comments come across as constructive. Those who have read this far down in the article get to know that many of these issues are already fixed or at least on the way to being fixed!</p>
<ul>
<li>Several gRPC implementations already support HTTP/3. ConnectRPC makes it pretty easy to use HTTP/3 with gRPC (I&rsquo;ll follow up on this in a future post).</li>
<li>Since the <a href="https://protobuf.dev/programming-guides/proto3/#json" rel="external">protobuf spec has a canonical mapping to/from JSON</a> I no longer have to worry about the text format. I really do hope that everyone forgets that it exists. There&rsquo;s only room for so many text-based formats. I wasn&rsquo;t joking about that. This is the last time I&rsquo;m acknowledging its existence.</li>
<li>The gRPC community is actually alive and well if you know where to look. For example, the <a href="https://buf.build/links/slack" rel="external">buf slack</a> has been a great resource for me. You may find me hanging out and answering questions fairly often.</li>
<li>The <a href="https://buf.build/docs/ecosystem/cli-overview" rel="external">Buf CLI</a> is an amazing tool for gRPC. It completely replaces <code>protoc</code> but also adds linting, breaking change detection, curl for gRPC, integration with the Buf Schema Registry (wow, real dependency management!) and more! In addition, more tools that you know and love from HTTP support gRPC like <a href="https://blog.postman.com/postman-now-supports-grpc/" rel="external">Postman</a>, <a href="https://docs.insomnia.rest/insomnia/grpc" rel="external">Insomnia</a> and <a href="https://k6.io/docs/using-k6/protocols/grpc/" rel="external">k6</a>.</li>
</ul>
<p>Despite gRPC&rsquo;s undeniable successes, it&rsquo;s important to acknowledge the framework&rsquo;s shortcomings to ensure its continued evolution and improvement. By addressing its learning curve, compatibility issues, lack of standardization, and community engagement, we can unlock gRPC&rsquo;s full potential and make it a more accessible and user-friendly tool for all developers.</p>
]]></content:encoded></item><item><title>Unit Testing ConnectRPC Servers</title><link>https://kmcd.dev/posts/connectrpc-unittests/</link><pubDate>Tue, 11 Jun 2024 11:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/connectrpc-unittests/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/connectrpc-unittests/cover_hu_15a2717b59d9d13e.webp" /> &lt;/p>
                
                Learn how to test your ConnectRPC services.
                </description><content:encoded><![CDATA[<p>If you&rsquo;ve embarked on the journey of building efficient and scalable RPC systems with <a href="https://connectrpc.com/" rel="external">ConnectRPC</a>, you might be pondering the best way to ensure the reliability and correctness of your services. Unit testing is the obvious tool for this, providing a safety net that catches bugs early and empowers you to refactor code fearlessly. In the ConnectRPC world, unit testing can be daunting due to its integration with Protocol Buffers and the client-server architecture. In this guide, we&rsquo;ll unravel the mysteries of unit testing ConnectRPC services, while arming you with practical examples and advanced techniques to fortify your codebase.</p>
<p>First off, the full source code can be found <a href="https://github.com/sudorandom/kmcd.dev/tree/main/content/posts/2024/connectrpc-unittests/go" rel="external">on github</a>. If it helps, feel free to download, run, and modify as you see fit!</p>
<h2 id="why-unit-test">Why Unit Test?</h2>
<p>Before we dive in, let&rsquo;s address the &ldquo;why.&rdquo; Unit testing your ConnectRPC servers brings a multitude of benefits:</p>
<ul>
<li><strong>Isolation:</strong> Focus on testing individual components in isolation, making it easier to pinpoint and fix issues.</li>
<li><strong>Speed:</strong> Unit tests execute quickly, providing fast feedback during development.</li>
<li><strong>Refactoring Confidence:</strong> When you have solid unit tests, you can refactor your code with confidence, knowing that the tests will catch any unintended consequences.</li>
<li><strong>Documentation:</strong> Well-written unit tests can serve as living documentation, illustrating how your code is meant to be used.</li>
<li><strong>Bug Prevention:</strong> A good suite of unit tests can help you catch bugs early on, before they become harder and more expensive to fix.</li>
</ul>
<h2 id="testing-strategies-with-connectrpc">Testing Strategies with ConnectRPC</h2>
<p>ConnectRPC, built upon the Protocol Buffers ecosystem, offers a couple of primary approaches to unit testing:</p>
<ol>
<li><strong>Direct Service Testing:</strong> This is ideal for unit testing but it&rsquo;s not always possible. You directly call the methods of your service implementation (typically a struct in Go), bypassing any client and server networking.</li>
<li><strong>Server Testing:</strong> This approach creates an actual ConnectRPC server with <code>net/http/httptest</code>. It&rsquo;s helpful when you want to test the interactions between your client and server code but is usually &ldquo;overkill&rdquo; unless you&rsquo;re wanting to test interceptors or HTTP middleware.</li>
</ol>
<h2 id="hands-on-our-example-service">Hands-On: Our example service</h2>
<p>Here is the protobuf file that we&rsquo;re using for our example:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">greet</span><span style="color:#81a1c1">.</span>v1<span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">option</span> go_package <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;example/gen/greet/v1;greetv1&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GreetRequest</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GreetResponse</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> greeting <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">service</span> GreetService <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">rpc</span> Greet<span style="color:#eceff4">(</span>GreetRequest<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span> <span style="color:#eceff4">(</span>GreetResponse<span style="color:#eceff4">)</span> <span style="color:#eceff4">{}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><aside>
See the full source at Github: <a href="https://github.com/sudorandom/kmcd.dev/blob/main/content/posts/2024/connectrpc-unittests/go/greet/v1/greet.proto" target="_blank">greet.proto</a>.

</aside>

<p>And here is the resulting server implementation:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> greeterService <span style="color:#81a1c1;font-weight:bold">struct</span><span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> _ greetv1connect<span style="color:#eceff4">.</span>GreetServiceHandler <span style="color:#eceff4">=</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">*</span>greeterService<span style="color:#eceff4">)(</span><span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>g <span style="color:#81a1c1">*</span>greeterService<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Greet</span><span style="color:#eceff4">(</span>ctx context<span style="color:#eceff4">.</span>Context<span style="color:#eceff4">,</span> req <span style="color:#81a1c1">*</span>connect<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">[</span>greetv1<span style="color:#eceff4">.</span>GreetRequest<span style="color:#eceff4">])</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">*</span>connect<span style="color:#eceff4">.</span>Response<span style="color:#eceff4">[</span>greetv1<span style="color:#eceff4">.</span>GreetResponse<span style="color:#eceff4">],</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> req<span style="color:#eceff4">.</span>Msg<span style="color:#eceff4">.</span>Name <span style="color:#81a1c1">==</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;missing name&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Simulate some network call that takes 10ms</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">select</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">case</span> <span style="color:#81a1c1">&lt;-</span>ctx<span style="color:#eceff4">.</span><span style="color:#88c0d0">Done</span><span style="color:#eceff4">():</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> ctx<span style="color:#eceff4">.</span><span style="color:#88c0d0">Err</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">case</span> <span style="color:#81a1c1">&lt;-</span>time<span style="color:#eceff4">.</span><span style="color:#88c0d0">After</span><span style="color:#eceff4">(</span><span style="color:#b48ead">10</span> <span style="color:#81a1c1">*</span> time<span style="color:#eceff4">.</span>Millisecond<span style="color:#eceff4">):</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewResponse</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>greetv1<span style="color:#eceff4">.</span>GreetResponse<span style="color:#eceff4">{</span>Greeting<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Hello, &#34;</span> <span style="color:#81a1c1">+</span> req<span style="color:#eceff4">.</span>Msg<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">}),</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><aside>
See the full source at Github: <a href="https://github.com/sudorandom/kmcd.dev/blob/main/content/posts/2024/connectrpc-unittests/go/endpoints.go" target="_blank">endpoints.go</a>.

</aside>

<p>Here we defined our <code>greetv1connect.GreetServiceHandler</code> implementation. It implements the <code>Greet</code> method defined in the protobuf file alove. Since this is for demonstration purposes, all we do is check to see if the given <code>name</code> is empty, sleep for 10 milliseconds to simulate a network call and returns the greeting as <code>&quot;Hello, {name}&quot;</code>.</p>
<p>A keep observer might notice that this file contains our first &ldquo;test&rdquo;. The line <code>var _ greetv1connect.GreetServiceHandler = (*greeterService)(nil)</code> is a way of doing a type assertion in Go. It ensures that your <code>greeterService</code> struct correctly implements the <code>GreeterService</code> interface defined by the protobuf file above. This relies on a trick of the Go syntax that will try to bind a variable <code>_</code> using the <code>greetv1connect.GreetServiceHandler</code> type. If the given <code>greeterService</code> pointer doesn&rsquo;t implement the interface then the compiler should complain about what specific methods are missing and which method signatures don&rsquo;t match.</p>
<h2 id="hands-on-direct-service-testing-example">Hands-On: Direct Service Testing Example</h2>
<p>Let&rsquo;s write some unit tests for a simple ConnectRPC service:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">TestGreet</span><span style="color:#eceff4">(</span>t <span style="color:#81a1c1">*</span>testing<span style="color:#eceff4">.</span>T<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	service <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>greeterService<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>	response<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> service<span style="color:#eceff4">.</span><span style="color:#88c0d0">Greet</span><span style="color:#eceff4">(</span>context<span style="color:#eceff4">.</span><span style="color:#88c0d0">Background</span><span style="color:#eceff4">(),</span> connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewRequest</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>greetv1<span style="color:#eceff4">.</span>GreetRequest<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Alice&#34;</span><span style="color:#eceff4">}))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		t<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Greet failed: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> response<span style="color:#eceff4">.</span>Msg<span style="color:#eceff4">.</span>Greeting <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#34;Hello, Alice&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		t<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Unexpected greeting: got %q, want %q&#34;</span><span style="color:#eceff4">,</span> response<span style="color:#eceff4">.</span>Msg<span style="color:#eceff4">.</span>Greeting<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;Hello, Alice&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><aside>
See the full source at Github: <a href="https://github.com/sudorandom/kmcd.dev/blob/main/content/posts/2024/connectrpc-unittests/go/direct_test.go" target="_blank">direct_test.go</a>.

</aside>

<p><strong>Explanation:</strong>
The <code>TestGreet</code> function creates an instance of your <code>greeterService</code> and directly calls its <code>Greet</code> method. We then assert that the response matches our expectations. This is, by far, the simplest method for testing a ConnectRPC service.</p>
<h2 id="hands-on-table-driven-tests-with-testify">Hands-On: Table-Driven Tests with Testify</h2>
<p>Now that we wrote a single unit test, the next example will show you how to utilize table tests in order to easily write more test cases. You will see code that looks like this in well-tested Go repositories.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">TestGreetTable</span><span style="color:#eceff4">(</span>t <span style="color:#81a1c1">*</span>testing<span style="color:#eceff4">.</span>T<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	service <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>greeterService<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>	cancelledCtx<span style="color:#eceff4">,</span> cancel <span style="color:#81a1c1">:=</span> context<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithCancel</span><span style="color:#eceff4">(</span>context<span style="color:#eceff4">.</span><span style="color:#88c0d0">Background</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>	<span style="color:#88c0d0">cancel</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	testCases <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1;font-weight:bold">struct</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		name    <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span>		ctx     context<span style="color:#eceff4">.</span>Context
</span></span><span style="display:flex;"><span>		req     <span style="color:#81a1c1">*</span>connect<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">[</span>greetv1<span style="color:#eceff4">.</span>GreetRequest<span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>		want    <span style="color:#81a1c1">*</span>connect<span style="color:#eceff4">.</span>Response<span style="color:#eceff4">[</span>greetv1<span style="color:#eceff4">.</span>GreetResponse<span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>		wantErr <span style="color:#81a1c1">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			name<span style="color:#eceff4">:</span>    <span style="color:#a3be8c">&#34;Success&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			req<span style="color:#eceff4">:</span>     connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewRequest</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>greetv1<span style="color:#eceff4">.</span>GreetRequest<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Bob&#34;</span><span style="color:#eceff4">}),</span>
</span></span><span style="display:flex;"><span>			want<span style="color:#eceff4">:</span>    connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewResponse</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>greetv1<span style="color:#eceff4">.</span>GreetResponse<span style="color:#eceff4">{</span>Greeting<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Hello, Bob&#34;</span><span style="color:#eceff4">}),</span>
</span></span><span style="display:flex;"><span>			wantErr<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			name<span style="color:#eceff4">:</span>    <span style="color:#a3be8c">&#34;Empty Name&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			req<span style="color:#eceff4">:</span>     connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewRequest</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>greetv1<span style="color:#eceff4">.</span>GreetRequest<span style="color:#eceff4">{}),</span>
</span></span><span style="display:flex;"><span>			want<span style="color:#eceff4">:</span>    <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span> <span style="color:#616e87;font-style:italic">// Expecting an error</span>
</span></span><span style="display:flex;"><span>			wantErr<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;missing name&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			name<span style="color:#eceff4">:</span>    <span style="color:#a3be8c">&#34;Context Cancelled&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			ctx<span style="color:#eceff4">:</span>     cancelledCtx<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			req<span style="color:#eceff4">:</span>     connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewRequest</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>greetv1<span style="color:#eceff4">.</span>GreetRequest<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Alice&#34;</span><span style="color:#eceff4">}),</span>
</span></span><span style="display:flex;"><span>			want<span style="color:#eceff4">:</span>    <span style="color:#81a1c1;font-weight:bold">nil</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			wantErr<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;context canceled&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> _<span style="color:#eceff4">,</span> tc <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> testCases <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		t<span style="color:#eceff4">.</span><span style="color:#88c0d0">Run</span><span style="color:#eceff4">(</span>tc<span style="color:#eceff4">.</span>name<span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>t <span style="color:#81a1c1">*</span>testing<span style="color:#eceff4">.</span>T<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			ctx <span style="color:#81a1c1">:=</span> tc<span style="color:#eceff4">.</span>ctx
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> ctx <span style="color:#81a1c1">==</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				ctx <span style="color:#eceff4">=</span> context<span style="color:#eceff4">.</span><span style="color:#88c0d0">Background</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>			got<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> service<span style="color:#eceff4">.</span><span style="color:#88c0d0">Greet</span><span style="color:#eceff4">(</span>ctx<span style="color:#eceff4">,</span> tc<span style="color:#eceff4">.</span>req<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> tc<span style="color:#eceff4">.</span>wantErr <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#34;&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				require<span style="color:#eceff4">.</span><span style="color:#88c0d0">Error</span><span style="color:#eceff4">(</span>t<span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>				assert<span style="color:#eceff4">.</span><span style="color:#88c0d0">Contains</span><span style="color:#eceff4">(</span>t<span style="color:#eceff4">,</span> err<span style="color:#eceff4">.</span><span style="color:#88c0d0">Error</span><span style="color:#eceff4">(),</span> tc<span style="color:#eceff4">.</span>wantErr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span> <span style="color:#81a1c1;font-weight:bold">else</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				require<span style="color:#eceff4">.</span><span style="color:#88c0d0">NoError</span><span style="color:#eceff4">(</span>t<span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>				assert<span style="color:#eceff4">.</span><span style="color:#88c0d0">Equal</span><span style="color:#eceff4">(</span>t<span style="color:#eceff4">,</span> tc<span style="color:#eceff4">.</span>want<span style="color:#eceff4">,</span> got<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><aside>
See the full source at Github: <a href="https://github.com/sudorandom/kmcd.dev/blob/main/content/posts/2024/connectrpc-unittests/go/table_test.go" target="_blank">table_test.go</a>.

</aside>

<p><strong>Explanation:</strong></p>
<ol>
<li><strong>Table Setup:</strong> A <code>testCases</code> slice defines scenarios with varying inputs (<code>req</code>), expected outputs (<code>want</code>), and potential errors (<code>wantErr</code>).</li>
<li><strong>Context Cancellation:</strong> The test case &ldquo;Context Cancelled&rdquo; simulates a cancelled context by creating a context with <code>context.WithCancel</code> and immediately calling <code>cancel()</code>.</li>
<li><strong>Testify Assertions:</strong> The <code>require</code> package is used for assertions that should stop the test if they fail (e.g., requiring an error). The <code>assert</code> package is used for assertions that are not critical for continuing the test. Typically, errors during test setup use <code>require</code> and assertions on the results of the test use <code>assert</code>.</li>
</ol>
<h2 id="hands-on-server-testing-example">Hands-On: Server Testing Example</h2>
<p>Here&rsquo;s how you can test the same service using <code>net/http/httptest</code> server:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">TestGreetWithServer</span><span style="color:#eceff4">(</span>t <span style="color:#81a1c1">*</span>testing<span style="color:#eceff4">.</span>T<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	mux <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServeMux</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">Handle</span><span style="color:#eceff4">(</span>greetv1connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewGreetServiceHandler</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>greeterService<span style="color:#eceff4">{}))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	server <span style="color:#81a1c1">:=</span> httptest<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServer</span><span style="color:#eceff4">(</span>mux<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	t<span style="color:#eceff4">.</span><span style="color:#88c0d0">Cleanup</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span> server<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	client <span style="color:#81a1c1">:=</span> greetv1connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewGreetServiceClient</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>DefaultClient<span style="color:#eceff4">,</span> server<span style="color:#eceff4">.</span>URL<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	response<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> client<span style="color:#eceff4">.</span><span style="color:#88c0d0">Greet</span><span style="color:#eceff4">(</span>context<span style="color:#eceff4">.</span><span style="color:#88c0d0">Background</span><span style="color:#eceff4">(),</span> connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewRequest</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>greetv1<span style="color:#eceff4">.</span>GreetRequest<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Alice&#34;</span><span style="color:#eceff4">}))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		t<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Greet failed: %v&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> response<span style="color:#eceff4">.</span>Msg<span style="color:#eceff4">.</span>Greeting <span style="color:#81a1c1">!=</span> <span style="color:#a3be8c">&#34;Hello, Alice&#34;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		t<span style="color:#eceff4">.</span><span style="color:#88c0d0">Errorf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Unexpected greeting: got %q, want %q&#34;</span><span style="color:#eceff4">,</span> response<span style="color:#eceff4">.</span>Msg<span style="color:#eceff4">.</span>Greeting<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;Hello, Alice&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><aside>
See the full source at Github: <a href="https://github.com/sudorandom/kmcd.dev/blob/main/content/posts/2024/connectrpc-unittests/go/server_test.go" target="_blank">server_test.go</a>.

</aside>

<ul>
<li><strong>Server Setup:</strong> We create a ConnectRPC handler and start it with <code>httptest.NewServer(mux)</code>.</li>
<li><strong>Client Setup:</strong> We create a ConnectRPC client that connects to the server that we just created.</li>
<li><strong>Test Interaction:</strong> We use the client to call the Greet method and assert the response, just like in the direct service testing example.</li>
</ul>
<h2 id="conclusion-test-with-confidence">Conclusion: Test with Confidence</h2>
<p>In this guide, we&rsquo;ve explored the &ldquo;why&rdquo; and &ldquo;how&rdquo; of unit testing your ConnectRPC services. By embracing unit testing as a core part of your development workflow, you&rsquo;ll create more robust, reliable, and maintainable RPC systems. Remember, effective testing isn&rsquo;t just about fixing bugs – it&rsquo;s about building confidence in your codebase and enabling you to iterate and evolve your services with ease.</p>
<p>The full source code can be found <a href="https://github.com/sudorandom/kmcd.dev/tree/main/content/posts/2024/connectrpc-unittests/go" rel="external">on github</a>.</p>
<p><strong>Next Steps:</strong></p>
<ul>
<li><strong>Go Beyond the Basics:</strong> Explore more advanced testing techniques, such as mocking dependencies for more complex scenarios.</li>
<li><strong>Integrate with Your CI/CD:</strong> Automate your unit tests to run as part of your continuous integration and continuous delivery (CI/CD) pipeline for immediate feedback on code changes.</li>
<li><strong>Share Your Knowledge:</strong> Help the ConnectRPC community grow by sharing your own testing strategies and experiences!</li>
</ul>
<p>Ready to put your newfound knowledge into action? Start writing those unit tests and watch your ConnectRPC projects thrive!</p>
]]></content:encoded></item><item><title>Daily Prompts</title><link>https://kmcd.dev/posts/daily-prompts/</link><pubDate>Tue, 04 Jun 2024 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/daily-prompts/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/daily-prompts/cover_hu_9224a1a8ebaf8ea.webp" /> &lt;/p>
                
                Kickstart your creative juices with a daily writing prompt.
                </description><content:encoded><![CDATA[<p>You may have noticed from my <a href="https://infosec.exchange/@sudorandom" rel="external">mastodon feed</a> or from a link on the website&rsquo;s navigation that I have added <strong>daily prompts</strong>. Each day is a new question to answer. You can respond to the post on Mastodon and it will appear (after some minutes) on this website. I&rsquo;ve been doing it for the entire month of May and I intend to keep doing it to spur my thoughts and maybe others. I feel like having a question to answer each day helps give others context on who I am, what I&rsquo;m doing and how I see the world. I have also found that it acts as a good way to reflect on things. I&rsquo;ve wanted some answers to be different&hellip; so I now have some motivation to make them different.</p>
<p><strong>So go and check out the <a href="https://kmcd.dev/prompts/">prompts page</a>.</strong></p>
<p>The rest of this article talks about all of the pieces that went into making this happen behind the scenes, besides actually writing the prompts.</p>
<h3 id="banner-images">Banner Images</h3>
<p>I wanted to show a banner image for the prompts&hellip; but using the same one every day seemed boring&hellip; So now I have a set of 6 banner images that I rotate through. Here&rsquo;s how I did that with Hugo:</p>
<p><code>layouts/partials/prompts/load-cover.html</code></p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#eceff4">{{</span><span style="color:#81a1c1">-</span> <span style="color:#bf616a">$</span>images <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">(</span>resources<span style="color:#eceff4">.</span>Match <span style="color:#a3be8c">&#34;images/prompt_covers/*&#34;</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">-</span><span style="color:#eceff4">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">{{</span><span style="color:#81a1c1">-</span> <span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#eceff4">(</span>index <span style="color:#bf616a">$</span><span style="color:#88c0d0">images</span> <span style="color:#eceff4">(</span>mod <span style="color:#eceff4">.</span>Date<span style="color:#eceff4">.</span><span style="color:#88c0d0">Day</span> <span style="color:#eceff4">(</span>len <span style="color:#bf616a">$</span>images<span style="color:#eceff4">)))</span> <span style="color:#81a1c1">-</span><span style="color:#eceff4">}}</span>
</span></span></code></pre></div><p>This piece of code picks a new cover based on the day of the month, so the first of every month will have the same cover, for example. As I add more covers, the covers might shift over some number of days but I hope to eventually make 31 covers so each day of the month is unique. Notice that I used the <code>return</code> statement. This is a <a href="https://gohugo.io/functions/go-template/return/" rel="external">special Hugo-specific statement</a> that allows you to return values from partials.</p>
<p>Here&rsquo;s an example how how I use this partial in another template:</p>
<p><code>layouts/prompts/single.html</code></p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#eceff4">{{</span><span style="color:#81a1c1">-</span> <span style="color:#bf616a">$</span>cover <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">(</span>partial <span style="color:#a3be8c">&#34;prompts/load-cover.html&#34;</span> <span style="color:#eceff4">.).</span>Fill <span style="color:#a3be8c">&#34;1520x400 Center webp q100&#34;</span> <span style="color:#81a1c1">-</span><span style="color:#eceff4">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">&lt;</span>figure<span style="color:#eceff4">&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">&lt;</span>img src<span style="color:#eceff4">=</span><span style="color:#a3be8c">&#34;{{ $cover.RelPermalink }}&#34;</span> alt<span style="color:#eceff4">=</span><span style="color:#a3be8c">&#34;{{ $cover.Title }}&#34;</span> <span style="color:#81a1c1">/</span><span style="color:#eceff4">&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">&lt;</span><span style="color:#81a1c1">/</span>figure<span style="color:#eceff4">&gt;</span>
</span></span></code></pre></div><h3 id="mastodon-comments">Mastodon &ldquo;Comments&rdquo;</h3>
<p>I&rsquo;ve changed a lot about how this website is deployed lately and hopefully, no one noticed&hellip; The biggest change was switching from <a href="https://pages.github.com/" rel="external">GitHub Pages</a> to <a href="https://pages.cloudflare.com/" rel="external">Cloudflare Pages</a>. At first, this was just a temporary change, because Cloudflare was going to make me wait an entire week before allowing me to use <code>kmcd.dev</code> with the pages website, so I adjusted the Github Action to publish to Cloudflare Pages instead and everything went pretty great with that. That got me thinking&hellip; I was scheduling GitHub Actions to post daily updates and I also had a separate posting schedule for Tuesday to publish a new post. That seemed both wasteful and limiting at the same time. So I came up with a plan.</p>
<p>Instead of using Github Actions or Cloudflare Workers to deploy my static website, I&rsquo;m just going to do it myself, from my Synology NAS. This allowed me to have a much more frequent update schedule for my website if there were changes. You may ask: what does it mean to &ldquo;have changes&rdquo; for a static website? Changes can be one of the following:</p>
<ul>
<li>I committed some changes that affected the website output</li>
<li>A scheduled post becomes live</li>
<li>A new mastodon post happened on my feed or there are other changes with the post (likes, favorites, boosts, edits etc.)</li>
</ul>
<div class="container">
  <pre class="mermaid">graph TD
    deployment[Deployment Script]
    cloudflarepages[fa:fa-cloud Cloudflare Pages]
    hugo[Hugo]
    deployment -- Git --> commits[Git Commits]
    deployment -- Git --> schedule[Scheduled Content]
    deployment -- Mastodon API --> mastodon[Mastodon Updates]
    commits --> hugo
    schedule --> hugo
    mastodon --> hugo
    hugo -- Wrangler --> cloudflarepages
  </pre>
</div>
<p>I have a simple script that checks for new changes in my Github repo periodically and does a rebuild to see if there are any scheduled changes. However, you may be wondering how I add mastodon posts to my &ldquo;static&rdquo; website. If you experiment enough, you may see that replies get added to my website within 10 minutes (if posted during the day in the European CEST timezone, otherwise it waits until the morning). I made this happen by writing some code with Go and a library called <a href="https://github.com/mattn/go-mastodon" rel="external">go-mastodon</a> that scans through my previous mastodon posts and puts the updated statuses into a Github repo which is updated for each build of my website. If I want to block content from appearing on my website, I can block the user or mute the reply.</p>
<p>The system works out pretty well. I can improve this, however. I could use a webhook to trigger builds more quickly when there are changes to my Github repo or if there are new posts on mastodon. As it stands, I have to wait up to 10 minutes to see the changes reflected&hellip; which isn&rsquo;t bad, it could just be better. This doesn&rsquo;t help with scheduled posts, but it would reduce the frequency that I have to check for updates on a timer.</p>
<p>I intend to polish the script that I use to pull down Mastodon updates and publish it as a tool that others can use. I may try to also add support for Blue Sky as well but I make no promises!</p>
<h3 id="enjoy">Enjoy</h3>
<p>I do hope that others find the prompts useful. If you have a suggestion for a prompt or any questions about my setup, please let me know in the comments for <em>this</em> post!</p>
]]></content:encoded></item><item><title>Adding chart.js to Hugo</title><link>https://kmcd.dev/posts/hugo-chartjs/</link><pubDate>Tue, 28 May 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/hugo-chartjs/</guid><description><![CDATA[ 
                <p> <img hspace="5" src="https://kmcd.dev/posts/hugo-chartjs/cover_hu_881193b9040d3d1d.webp" /> </p>
                
                Let&#39;s find out how to add chart.js to your static website built with Hugo.
                ]]></description><content:encoded><![CDATA[<p>I recently wanted to add some charts to a blog post and <a href="https://mermaid.live" rel="external">mermaid</a> just wasn&rsquo;t cutting it. Mermaid didn&rsquo;t support the options I wanted to use and ultimately wasn&rsquo;t flexible enough to show a horizontal bar chart with the customization options I wanted. So I went looking for alternatives&hellip; and that&rsquo;s when I found this <a href="https://github.com/shen-yu/hugo-chart" rel="external">shen-yu/hugo-chart</a>&hellip; layout? for hugo that adds <a href="https://www.chartjs.org/" rel="external">Chart.js</a> support. Chart.js is a great javascript library for creating many kinds of charts with many customization options. <em>Perfect</em>, I thought. As I started using <code>shen/yu-hugo-chart</code> to add chart.js to my site, a few things stood out to me:</p>
<ul>
<li>I felt like adding a &ldquo;layout&rdquo; and a new git submodule just for this was fairly extreme just to add support for a single javascript library.</li>
<li>The documentation on the <a href="https://www.chartjs.org/" rel="external">chart.js website</a> didn&rsquo;t match up with what was supported. I then realized that the hugo-chart plugin was using <em>Chart.js version 2</em> when the latest released version was v4&hellip; a whole two major versions behind. Yikes. So not only would this add a new git submodule, which is annoying, but it wasn&rsquo;t even up-to-date.</li>
</ul>
<p>At this point, I was ready to throw the entire project aside and do my own thing. This is what I ended up with. It only requires a single file to add the new shortcode. So instead of having a git dependency, just add this file to your shortcodes.</p>
<h3 id="installation">Installation</h3>
<p>Add this file at the path <code>layouts/shortcodes/chart.html</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>{{- if not (.Page.Scratch.Get &#34;hasChartJS&#34;) -}}
</span></span><span style="display:flex;"><span><span style="color:#eceff4">&lt;</span><span style="color:#81a1c1">script</span> <span style="color:#8fbcbb">src</span><span style="color:#81a1c1">=</span><span style="color:#a3be8c">&#34;https://cdn.jsdelivr.net/npm/chart.js&#34;</span><span style="color:#eceff4">&gt;&lt;/</span><span style="color:#81a1c1">script</span><span style="color:#eceff4">&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">&lt;</span><span style="color:#81a1c1">script</span><span style="color:#eceff4">&gt;</span> Chart<span style="color:#eceff4">.</span>defaults<span style="color:#eceff4">.</span>color <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#39;#fff&#39;</span><span style="color:#eceff4">;</span> <span style="color:#eceff4">&lt;/</span><span style="color:#81a1c1">script</span><span style="color:#eceff4">&gt;</span>
</span></span><span style="display:flex;"><span>{{- .Page.Scratch.Set &#34;hasChartJS&#34; true -}}
</span></span><span style="display:flex;"><span>{{- end -}}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{{- $id := substr (md5 .Inner) 0 16 -}}
</span></span><span style="display:flex;"><span><span style="color:#eceff4">&lt;</span><span style="color:#81a1c1">div</span> <span style="color:#8fbcbb">class</span><span style="color:#81a1c1">=</span><span style="color:#a3be8c">&#34;chart&#34;</span><span style="color:#eceff4">&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">&lt;</span><span style="color:#81a1c1">canvas</span> <span style="color:#8fbcbb">id</span><span style="color:#81a1c1">=</span><span style="color:#a3be8c">&#34;{{ $id }}&#34;</span><span style="color:#eceff4">&gt;&lt;/</span><span style="color:#81a1c1">canvas</span><span style="color:#eceff4">&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">&lt;/</span><span style="color:#81a1c1">div</span><span style="color:#eceff4">&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">&lt;</span><span style="color:#81a1c1">script</span><span style="color:#eceff4">&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">document</span><span style="color:#eceff4">.</span>addEventListener<span style="color:#eceff4">(</span><span style="color:#a3be8c">&#39;DOMContentLoaded&#39;</span><span style="color:#eceff4">,</span> <span style="color:#eceff4">()</span> <span style="color:#eceff4">=&gt;</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1;font-weight:bold">var</span> ctx <span style="color:#81a1c1">=</span> <span style="color:#81a1c1">document</span><span style="color:#eceff4">.</span>getElementById<span style="color:#eceff4">(</span><span style="color:#a3be8c">&#39;{{ $id }}&#39;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1;font-weight:bold">var</span> options <span style="color:#81a1c1">=</span> <span style="color:#eceff4">{{</span> <span style="color:#eceff4">.</span>Inner <span style="color:#81a1c1">|</span> chomp <span style="color:#81a1c1">|</span> safeJS <span style="color:#eceff4">}};</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1;font-weight:bold">new</span> Chart<span style="color:#eceff4">(</span>ctx<span style="color:#eceff4">,</span> options<span style="color:#eceff4">);</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">});</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">&lt;/</span><span style="color:#81a1c1">script</span><span style="color:#eceff4">&gt;</span>
</span></span></code></pre></div><p>The <code>.Page.Scratch.Set</code> and <code>.Page.Scratch.Get</code> calls allows me to only have the <code>&lt;script&gt;</code> tag that imports chart.js a single time, which seems cleaner to me than importing it for each call to the chart shortcode.</p>
<p>Once this file is in place, you can now call the <code>chart</code> shortcode like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#eceff4">{{</span><span style="color:#81a1c1">&lt;</span> chart <span style="color:#81a1c1">&gt;</span><span style="color:#eceff4">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  type<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#39;type&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  data<span style="color:#81a1c1">:</span> <span style="color:#eceff4">{...},</span>
</span></span><span style="display:flex;"><span>  options<span style="color:#81a1c1">:</span> <span style="color:#eceff4">{...}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">{{</span><span style="color:#81a1c1">&lt;</span> <span style="color:#bf616a">/chart &gt;}}</span>
</span></span></code></pre></div><p>and a chart will be generated using chart.js. A few full examples are below:</p>
<h3 id="line-chart">Line Chart</h3>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#eceff4">{{</span><span style="color:#81a1c1">&lt;</span> chart <span style="color:#81a1c1">&gt;</span><span style="color:#eceff4">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  type<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#39;line&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  data<span style="color:#81a1c1">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>      labels<span style="color:#81a1c1">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#39;Jan&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#39;Feb&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#39;Mar&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#39;Apr&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#39;May&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#39;June&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#39;July&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>    datasets<span style="color:#81a1c1">:</span> <span style="color:#eceff4">[{</span>
</span></span><span style="display:flex;"><span>      label<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#39;My First Dataset&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      data<span style="color:#81a1c1">:</span> <span style="color:#eceff4">[</span><span style="color:#b48ead">65</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">59</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">80</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">81</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">56</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">55</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">40</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>      fill<span style="color:#81a1c1">:</span> <span style="color:#81a1c1;font-weight:bold">false</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      borderColor<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#39;rgb(75, 192, 192)&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      tension<span style="color:#81a1c1">:</span> <span style="color:#b48ead">0.1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}]</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">{{</span><span style="color:#81a1c1">&lt;</span> <span style="color:#bf616a">/chart &gt;}}</span>
</span></span></code></pre></div><p><script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script> Chart.defaults.color = '#fff'; </script><div class="chart">
    <canvas id="c509726fdfe5fe18"></canvas>
</div>
<script>
    document.addEventListener('DOMContentLoaded', () => {
        var ctx = document.getElementById('c509726fdfe5fe18')
        var options = 
{
  type: 'line',
  data: {
      labels: [
        'Jan',
        'Feb',
        'Mar',
        'Apr',
        'May',
        'June',
        'July'
      ],
    datasets: [{
      label: 'My First Dataset',
      data: [65, 59, 80, 81, 56, 55, 40],
      fill: false,
      borderColor: 'rgb(75, 192, 192)',
      tension: 0.1
    }]
  },
};
        new Chart(ctx, options);
    });
</script>

See more options on line charts <a href="https://www.chartjs.org/docs/latest/charts/line.html" rel="external">here</a>.</p>
<h3 id="bar-chart">Bar Chart</h3>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#eceff4">{{</span><span style="color:#81a1c1">&lt;</span> chart <span style="color:#81a1c1">&gt;</span><span style="color:#eceff4">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    type<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#39;bar&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    data<span style="color:#81a1c1">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        labels<span style="color:#81a1c1">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a3be8c">&#39;Dataset 1&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a3be8c">&#39;Dataset 2&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a3be8c">&#39;Dataset 3&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a3be8c">&#39;Dataset 4&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a3be8c">&#39;Dataset 5&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a3be8c">&#39;Dataset 6&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>        datasets<span style="color:#81a1c1">:</span> <span style="color:#eceff4">[{</span>
</span></span><span style="display:flex;"><span>            label<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#39;units&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            data<span style="color:#81a1c1">:</span> <span style="color:#eceff4">[</span><span style="color:#b48ead">36</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">50</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">3</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">36</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">63</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">79</span><span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    options<span style="color:#81a1c1">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        indexAxis<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#39;y&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        plugins<span style="color:#81a1c1">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>            legend<span style="color:#81a1c1">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>                display<span style="color:#81a1c1">:</span> <span style="color:#81a1c1;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>            <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>            title<span style="color:#81a1c1">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>                display<span style="color:#81a1c1">:</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>                text<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#39;Bar Chart title&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">{{</span><span style="color:#81a1c1">&lt;</span> <span style="color:#bf616a">/chart &gt;}}</span>
</span></span></code></pre></div><p><div class="chart">
    <canvas id="d554f868eecfeeee"></canvas>
</div>
<script>
    document.addEventListener('DOMContentLoaded', () => {
        var ctx = document.getElementById('d554f868eecfeeee')
        var options = 
{
    type: 'bar',
    data: {
        labels: [
          'Dataset 1',
          'Dataset 2',
          'Dataset 3',
          'Dataset 4',
          'Dataset 5',
          'Dataset 6'
        ],
        datasets: [{
            label: 'units',
            data: [36, 50, 3, 36, 63, 79]
        }]
    },
    options: {
        indexAxis: 'y',
        plugins: {
            legend: {
                display: false
            },
            title: {
                display: true,
                text: 'Bar Chart title'
            }
        }
    }
};
        new Chart(ctx, options);
    });
</script>

See more options on bar charts <a href="https://www.chartjs.org/docs/latest/charts/bar.html" rel="external">here</a>.</p>
<h3 id="polar-area-chart">Polar Area Chart</h3>
<p>You can use any chart <a href="https://www.chartjs.org/docs/latest/" rel="external">available in chart.js</a>. Here&rsquo;s a polar area chart:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#eceff4">{{</span><span style="color:#81a1c1">&lt;</span> chart <span style="color:#81a1c1">&gt;</span><span style="color:#eceff4">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>  type<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#39;polarArea&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>  data<span style="color:#81a1c1">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    labels<span style="color:#81a1c1">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#39;Red&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#39;Green&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#39;Yellow&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#39;Grey&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a3be8c">&#39;Blue&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>    datasets<span style="color:#81a1c1">:</span> <span style="color:#eceff4">[{</span>
</span></span><span style="display:flex;"><span>      label<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#39;My First Dataset&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>      data<span style="color:#81a1c1">:</span> <span style="color:#eceff4">[</span><span style="color:#b48ead">11</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">16</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">3</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">14</span><span style="color:#eceff4">],</span>
</span></span><span style="display:flex;"><span>      backgroundColor<span style="color:#81a1c1">:</span> <span style="color:#eceff4">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#39;rgb(255, 99, 132)&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#39;rgb(75, 192, 192)&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#39;rgb(255, 205, 86)&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#39;rgb(201, 203, 207)&#39;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#39;rgb(54, 162, 235)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#eceff4">]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}]</span>
</span></span><span style="display:flex;"><span>  <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>  options<span style="color:#81a1c1">:</span> <span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">{{</span><span style="color:#81a1c1">&lt;</span> <span style="color:#bf616a">/chart &gt;}}</span>
</span></span></code></pre></div><p><div class="chart">
    <canvas id="bb5f680f8b9641ac"></canvas>
</div>
<script>
    document.addEventListener('DOMContentLoaded', () => {
        var ctx = document.getElementById('bb5f680f8b9641ac')
        var options = 
{
  type: 'polarArea',
  data: {
    labels: [
      'Red',
      'Green',
      'Yellow',
      'Grey',
      'Blue'
    ],
    datasets: [{
      label: 'My First Dataset',
      data: [11, 16, 7, 3, 14],
      backgroundColor: [
        'rgb(255, 99, 132)',
        'rgb(75, 192, 192)',
        'rgb(255, 205, 86)',
        'rgb(201, 203, 207)',
        'rgb(54, 162, 235)'
      ]
    }]
  },
  options: {}
};
        new Chart(ctx, options);
    });
</script>

See more options on bar charts <a href="https://www.chartjs.org/docs/latest/charts/polar.html" rel="external">here</a>.</p>
<h3 id="some-other-kinds">Some other kinds</h3>
<p>I&rsquo;m going to spare you from giving an example of every type of chart. Please reference the official <a href="https://www.chartjs.org/docs/latest/" rel="external">chart.js documentation</a> to see all available options.</p>
<h3 id="end">End</h3>
<p>I introduced a way to add a fully up-to-date chart.js to your hugo website that avoids many of the downsides of existing solutions. If you liked this post, feel free to send me any comments or questions <a href="https://infosec.exchange/@sudorandom" rel="external">on mastodon</a>!</p>
]]></content:encoded></item><item><title>Why I'm Rebranding</title><link>https://kmcd.dev/posts/why-im-rebranding/</link><pubDate>Thu, 23 May 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/why-im-rebranding/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/why-im-rebranding/cover_hu_56bfd0b4654a8def.webp" /> &lt;/p>
                
                Find out why the name of this website changed
                </description><content:encoded><![CDATA[<p>For the longest time, I&rsquo;ve had the alias <code>sudorandom</code>. At this point, it feels like just another name. So why did I rebrand this website from <code>sudorandom.dev</code> to <code>kmcd.dev</code>? Let&rsquo;s dig into it.</p>
<h2 id="i-am-me">I am me</h2>
<p>My name is <strong>Kevin McDonald</strong>. The name isn&rsquo;t going away any time soon, so it simply makes sense to use my name in my personal branding online. And while I think <code>sudorandom</code> is a brilliant pun, I don&rsquo;t want to immediately drive away those who don&rsquo;t immediately understand the pun or, worse, think that I just can&rsquo;t spell &lsquo;pseudorandom&rsquo;.</p>
<p>I am not currently looking to reach a broader audience, but I think it&rsquo;s useful to have a branding that isn&rsquo;t appealing to only a small subset of people. <code>kmcd.dev</code>, since it&rsquo;s a shortening of my name, transcends any niche linux/computer science reference.</p>
<h2 id="professionalism">professionalism</h2>
<p>I still want to keep a casual tone to the website for now, but I think my website fails the simple test of being able to verbally explain the name of your website without having to awkwardly guess how proficient in the tech world the other person is. I think <code>kmcd.dev</code> easily passes this test and is frankly very nice to type. I&rsquo;ve typed it a few times already in this post and it just feels snappy.</p>
<p>You should see how it feels by sending my website to a few friends. <em>(hey! it&rsquo;s worth a shot!)</em></p>
<h2 id="seo">SEO</h2>
<p>Initially, when I searched for &ldquo;sudorandom&rdquo; on google, it would redirect me to &ldquo;pseudorandom&rdquo;. It was literally impossible to search for my website using the exact name. This appears to have been changed but I think it does signal a problem with the name from an SEO standpoint.</p>
<p>I know my name isn&rsquo;t the most unique. The more famous of the men sharing my name is a <a href="https://en.wikipedia.org/wiki/Kevin_McDonald" rel="external">Canadian actor</a>. But he&rsquo;s quite a bit older than me so what I should really be fighting for is to beat the <a href="https://en.wikipedia.org/wiki/Kevin_McDonald_%28footballer,_born_1988%29" rel="external">Scottish footballer</a>, who was born in the same year I was. But my point here is that using an alias like <code>sudorandom</code> doesn&rsquo;t help my name SEO at all.</p>
<p>Finally, it&rsquo;s four letters long. With a name this short it must have some SEO magic.</p>
<h2 id="longevity">longevity</h2>
<p>I&rsquo;m not sure what I will want to focus on in the future. I&rsquo;ve had periods where I was obsessed with a space-based MMO and another period where I loved learning about optical networking. Who knows, I may take up teaching people cringy Danish/English puns after my wife and daughter ban me from the practice around them.</p>
<p>The beauty of <code>kmcd.dev</code> is that it doesn&rsquo;t restrict me to a specific topic. As my interests evolve, the website name remains relevant, allowing me to explore new technologies and share my knowledge with you.</p>
<h2 id="skål">skål!</h2>
<p>Overall, rebranding to <code>kmcd.dev</code> offers a clearer, shorter, and more professional website name that strengthens my personal brand in the long run.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>iuuoekifqsusemreo
</span></span></code></pre></div>]]></content:encoded></item><item><title>Benchmarking gRPC (golang)</title><link>https://kmcd.dev/posts/benchmarking-go-grpc/</link><pubDate>Tue, 21 May 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/benchmarking-go-grpc/</guid><description><![CDATA[ 
                <p> <img hspace="5" src="https://kmcd.dev/posts/benchmarking-go-grpc/cover_hu_92bc57cd93f502e.webp" /> </p>
                
                Let&#39;s so how fast gRPC can go in Go.
                ]]></description><content:encoded><![CDATA[<p>Hey everyone, as you know from my previous posts I&rsquo;m a big fan of gRPC especially when working with Go. It&rsquo;s been my go-to tool for building remote procedure calls (RPCs) for a while now. It has been extremely reliable for providing high-performance RPC systems. Originally, there was only one choice: Google&rsquo;s <code>grpc-go</code> library. It came before HTTP/2 support landed in the Go standard library, and being from Google, the creators of gRPC, it seemed like the natural fit. But is it <em>actually</em> the best gRPC library for Go?</p>
<p>As time marches on, and with it, some concerns about <code>grpc-go</code> have emerged:</p>
<ul>
<li><strong>Experimental Features:</strong> Many important features are labeled <code>experimental</code>, meaning they could change or even disappear in future updates. This can cause compatibility issues down the road.</li>
<li><strong>Design Quirks:</strong> Some design choices feel a bit odd. Because it doesn&rsquo;t integrate well with any standard HTTP middleware or tooling then it makes for a more fragmented series of libraries that are specific to gRPC. Furthermore, widely used features are often deprecated, moved and removed, breaking compatibility for existing projects. There&rsquo;s also been a few <a href="https://github.com/grpc/grpc-go/commit/0f6ef0fbe51aa33d05a91d0fa87b28113b83f5a9" rel="external">missteps</a> around API management.</li>
<li><strong>Separate Server Dependency:</strong> <code>grpc-go</code> requires a separate server instead of leveraging the standard Go HTTP server. This might not be ideal if you need to serve other functionalities alongside your gRPC endpoints, like OAuth or large file uploads.</li>
</ul>
<h2 id="the-lineup">The lineup</h2>
<p>Because of these reasons, I decided to explore some alternatives to grpc-go and if I require similar performance, I should make sure that the alternatives are in the same ballpark of performance. This post dives into benchmarks comparing the performance of three different ways to run a gRPC server in Go:</p>
<ul>
<li><strong><a href="https://github.com/grpc/grpc-go" rel="external">grpc-go</a>:</strong> The official library from Google. See my implementation <a href="https://github.com/sudorandom/go-grpc-bench/blob/v0.0.1/cmd/grpc-go/main.go" rel="external">here</a>.</li>
<li><strong><a href="https://pkg.go.dev/google.golang.org/grpc#Server.ServeHTTP" rel="external">grpc-go-servehttp</a>:</strong> A variant of <code>grpc-go</code> that allows using the standard Go HTTP server. This is useful when you want to add extra HTTP routes alongside the gRPC ones without having to manage another HTTP server instance on a different port. See my implementation <a href="https://github.com/sudorandom/go-grpc-bench/blob/v0.0.1/cmd/grpc-go-servehttp/main.go" rel="external">here</a>.</li>
<li><strong><a href="https://github.com/connectrpc/connect-go" rel="external">connectrpc</a>:</strong> A third-party gRPC library that supports the Go HTTP server <a href="https://connectrpc.com/docs/multi-protocol/" rel="external">(and gRPC-Web and the Connect protocol)</a>. I wrote about this <a href="https://kmcd.dev/posts/connectrpc">previously</a>. See my implementation <a href="https://github.com/sudorandom/go-grpc-bench/blob/v0.0.1/cmd/connectrpc/main.go" rel="external">here</a>.</li>
</ul>
<p>Before diving into writing my own benchmarks, I wanted to see what exists today:</p>
<ul>
<li>The gRPC authors have published their own <strong><a href="https://grpc.io/docs/guides/benchmarking/" rel="external">benchmark results</a></strong>. In these results, it looks like grpc-go is among the top performers.</li>
<li><strong><a href="https://github.com/LesnyRumcajs/grpc_bench" rel="external">LesnyRumcajs/grpc_bench</a></strong> has a very good suite of gRPC frameworks in many different languages. The latest &ldquo;official&rdquo; results are <a href="https://github.com/LesnyRumcajs/grpc_bench/discussions/441" rel="external">here</a>. This paints a significantly different picture where grpc-go is in the &ldquo;middle of the pack&rdquo;.</li>
</ul>
<p>I wanted to reproduce what happens in grpc_bench but with Go&rsquo;s CPU profiling enabled so that I can dig in and see what parts of the code are taking longer. After trying to bootstrap it to grpc_bench for a while I ended up just making my own repo with benchmark tests just for Go libraries.</p>
<p><em>Aside:</em> Don&rsquo;t worry, before I did this I <a href="https://github.com/LesnyRumcajs/grpc_bench/pull/459" rel="external">contributed back</a> a change that updates all of the relevant libraries and the version of Go being used. I guessed that this might be some of the reason for the low ConnectRPC numbers because it was using a super old version from before they moved to <code>connectrpc.com/connect</code> as the canonical import.</p>
<p>In my new benchmark repo, I used <a href="https://ghz.sh/" rel="external">ghz</a> to run the benchmarks. I added the ability to capture pprof profiles while running the benchmark.</p>
<h2 id="test-one-the-empty-message-test">Test One: The Empty Message Test</h2>
<p>This scenario uses an empty message to gauge the overall performance of handling requests, without any of the overhead of protobuf parsing.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;proto&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;proto/flex.proto&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;call&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;flex.FlexService/NormalRPC&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;total&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">101000</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;skipFirst&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1000</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;concurrency&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">50</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;connections&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">20</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;data&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">{},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;max-duration&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;300s&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;host&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;0.0.0.0:6660&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;insecure&#34;</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p><a href="https://github.com/sudorandom/go-grpc-bench/blob/v0.0.1/scenarios/empty.json" rel="external">(source)</a></p>
<h3 id="results">Results</h3>
<p>The results are measured in Requests Per Second (RPS) which means that higher is better. This was run on an Intel NUC:</p>
<ul>
<li>Intel(R) Core(TM) i7-8705G CPU @ 3.10GHz</li>
<li>8GB of DDR4 RAM</li>
</ul>
<p>This is a pretty outdated machine so if you run these benchmarks locally, you should see better performance.</p>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script> Chart.defaults.color = '#fff'; </script><div class="chart">
    <canvas id="895666d3ebbc8c66"></canvas>
</div>
<script>
    document.addEventListener('DOMContentLoaded', () => {
        var ctx = document.getElementById('895666d3ebbc8c66')
        var options = 
{
    type: 'bar',
    data: {
        labels: ['grpc-go', 'grpc-go-servehttp', 'connectrpc'],
        datasets: [{
            label: 'rps',
            data: [20303, 14216, 16272],
            backgroundColor: [
                'rgba(255, 99, 132, 0.5)',
                'rgba(54, 162, 235, 0.5)',
                'rgba(255, 206, 86, 0.5)'
            ],
            borderColor: [
                'rgba(255, 99, 132, 1)',
                'rgba(54, 162, 235, 1)',
                'rgba(255, 206, 86, 1)'
            ],
            borderWidth: 1
        }]
    },
    options: {
        indexAxis: 'y',
        plugins: {
            legend: {
                display: false
            },
            title: {
                display: true,
                text: 'Empty Protobuf Message (higher is better)'
            }
        },
        scaleShowValues: true,
        scales: {
            yAxes: [{
                ticks: {
                    autoSkip: false
                }
            }],
            xAxes: [{
                ticks: {
                    autoSkip: false
                }
            }]
        }
    }
};
        new Chart(ctx, options);
    });
</script>

<p>This benchmark measured the performance of handling requests with an empty message. This means no actual data is being exchanged, so it focuses on the overhead of handling gRPC requests themselves.</p>
<ul>
<li><strong>grpc-go was the clear winner</strong>, processing over 20,000 requests per second.</li>
<li><strong>connectrpc followed closely behind</strong> at around 16,000 requests per second.</li>
<li><strong>grpc-go (with ServeHTTP) lagged behind</strong> at roughly 14,000 requests per second.</li>
</ul>
<h2 id="test-two-something-slightly-more-realistic">Test Two: Something slightly more realistic</h2>
<p>This scenario uses a larger message with most of the protobuf types to exercise all of the different code paths when marshaling and unmarshalling protobuf messages.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;proto&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;proto/flex.proto&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;call&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;flex.FlexService/NormalRPC&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;total&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">101000</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;skipFirst&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1000</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;concurrency&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">50</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;connections&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">20</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;data&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1">&#34;msg&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;doubleField&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">123.4567</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;floatField&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">123.4567</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;int32Field&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1234</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;int64Field&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1234567</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;uint32Field&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1234</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;uint64Field&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1234567</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;sint32Field&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1234</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;sint64Field&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1234567</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;fixed32Field&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1234</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;fixed64Field&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1234567</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;sfixed32Field&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1234</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;sfixed64Field&#34;</span><span style="color:#eceff4">:</span> <span style="color:#b48ead">1234567</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;boolField&#34;</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;stringField&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;hello world&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;msgField&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">{},</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;repeatedMsgField&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">[{},</span> <span style="color:#eceff4">{}],</span>
</span></span><span style="display:flex;"><span>            <span style="color:#81a1c1">&#34;optionalMsgField&#34;</span><span style="color:#eceff4">:</span> <span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;max-duration&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;300s&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;host&#34;</span><span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;0.0.0.0:6660&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&#34;insecure&#34;</span><span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p><a href="https://github.com/sudorandom/go-grpc-bench/blob/v0.0.1/scenarios/complex.json" rel="external">(source)</a></p>
<p>In this scenario, I also tested with and without an alternative marshal/unmarshal implementation provided by <a href="https://github.com/planetscale/vtprotobuf" rel="external">vtprotobuf</a>, which can further optimize performance. This was omitted from the last test because there shouldn&rsquo;t really be much performance difference when you&rsquo;re parsing an empty message.</p>
<h3 id="results-1">Results</h3>
<p>Now here are the results of a test that uses a relatively complex message so we can see how protobuf parsing factors into the benchmarks:
<div class="chart">
    <canvas id="23956b95815bd3a9"></canvas>
</div>
<script>
    document.addEventListener('DOMContentLoaded', () => {
        var ctx = document.getElementById('23956b95815bd3a9')
        var options = 
{
    type: 'bar',
    data: {
        labels: ['grpc-go', 'grpc-go (vtprotobuf)', 'grpc-go-servehttp', 'grpc-go-servehttp (vtprotobuf)', 'connectrpc', 'connectrpc (vtprotobuf)'],
        datasets: [{
            label: 'rps',
            data: [16836, 17450, 12403, 12836, 13963, 14079],
            backgroundColor: [
                'rgba(255, 99, 132, 0.5)',
                'rgba(255, 99, 132, 0.5)',
                'rgba(54, 162, 235, 0.5)',
                'rgba(54, 162, 235, 0.5)',
                'rgba(255, 206, 86, 0.5)',
                'rgba(255, 206, 86, 0.5)'
            ],
            borderColor: [
                'rgba(255, 99, 132, 1)',
                'rgba(255, 99, 132, 1)',
                'rgba(54, 162, 235, 1)',
                'rgba(54, 162, 235, 1)',
                'rgba(255, 206, 86, 1)',
                'rgba(255, 206, 86, 1)'
            ],
            borderWidth: 1
        }]
    },
    options: {
        indexAxis: 'y',
        plugins: {
            legend: {
                display: false
            },
            title: {
                display: true,
                text: 'More Complex Protobuf Message (higher is better)'
            }
        },
        scaleShowValues: true,
        scales: {
            yAxes: [{
                ticks: {
                    autoSkip: false
                }
            }],
            xAxes: [{
                ticks: {
                    autoSkip: false
                }
            }]
        }
    }
};
        new Chart(ctx, options);
    });
</script>
</p>
<p>The goal of this benchmark was to introduce a larger message with various data types to better simulate real-world gRPC communication. It examines how well each library handles message marshaling and unmarshaling on top of the request processing.</p>
<ul>
<li>Again, <strong>grpc-go came out on top</strong>, processing over 16,000 requests per second.</li>
<li>Even with a complex message, <strong>connectrpc stayed competitive</strong> at around 14,000 requests per second.</li>
<li><strong>grpc-go (with ServeHTTP) stayed in last place</strong> with nearly 13,000.</li>
<li>Note that <strong>vtprotobuf does help out a decent amount</strong>. vtprotobuf is being used in a mode that has zero extra hand-written code. The plugin also allows another optimization using pools which does require code changes. I may add that setup to these benchmarks at some point.</li>
</ul>
<h2 id="the-takeaway">The Takeaway</h2>
<p>If performance is the most important aspect of your application, stick with grpc-go. But if you want to add extra HTTP endpoints, add <a href="https://connectrpc.com/docs/multi-protocol" rel="external">gRPC-Web support</a> without an extra proxy, support for HTTP/1.1 or be able to <a href="https://connectrpc.com/docs/curl-and-other-clients/" rel="external">just use curl your gRPC endpoints</a> you might want to look into ConnectRPC.</p>
<p>I wouldn&rsquo;t recommend using <a href="https://pkg.go.dev/google.golang.org/grpc#Server.ServeHTTP" rel="external">grpc-go with ServeHTTP</a> because it&rsquo;s slower than ConnectRPC while offering fewer features. Maybe only if you&rsquo;re really entrenched into the grpc-go ecosystem should you consider using it.</p>
<h2 id="open-questions-and-next-steps">Open Questions and Next Steps</h2>
<p>I am curious about exactly why supporting the <code>ServeHTTP</code> interface causes such a drastic difference in performance for grpc-go. There must be some cost to supporting the <code>ServeHTTP</code> interface that you don&rsquo;t incur when implementing the HTTP/2 server or some kind of optimization that you can&rsquo;t do in a general case that grpc-go can do. If you have any ideas about this, I&rsquo;m happy to hear them.</p>
<p>I do have to give the standard disclaimer about benchmarks. This is an artificial benchmark that was run on my underpowered, hobbyist Intel NUC and while grpc-go performs well, the difference between it and the other methods is probably negligible for use cases that don&rsquo;t require peak performance.</p>
<p>The approach that ConnectRPC has with using ServeHTTP with a normal <code>http.Server</code> provided by the standard library means that it&rsquo;s likely to &ldquo;just work&rdquo; with http/3 <a href="https://github.com/golang/go/issues/32204" rel="external">when it lands in the Go standard library</a>. This is exciting for these artificial benchmarks (and some real use cases) where creating new connections is a significant part of the overhead.</p>
<p>See the full benchmark source here at <a href="https://github.com/sudorandom/go-grpc-bench/tree/v0.0.1" rel="external">github.com/sudorandom/go-grpc-bench</a>. The repo does contain CPU profile captures alongside the results which I was attempting to use to narrow down the performance differences, but I haven&rsquo;t found anything interesting there yet. If I succeed at finding anything interesting, I may post another update here!</p>
]]></content:encoded></item><item><title>Blog Update</title><link>https://kmcd.dev/posts/blog-update/</link><pubDate>Tue, 14 May 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/blog-update/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/blog-update/cover_hu_b2f16ab6ff96474e.webp" /> &lt;/p>
                
                An update about the state of the blog.
                </description><content:encoded><![CDATA[<p>Hello! This will be a lighter update for this blog. I&rsquo;m going to recap some of the changes I&rsquo;ve made to it over the last couple of months, the technologies that power it, and my plans for the future.</p>
<h2 id="schedule">Schedule</h2>
<p>I&rsquo;ve been keeping up a one-post-a-week cadence. This has started pretty well because I was in a hyper-productive mode and built up a fairly sizable backlog but I&rsquo;m now in a less productive phase. Therefore, some weeks in the future may be &ldquo;softer&rdquo; than others and I may resort to link posts, commenting about recent events, and other lower-effort ways of getting more words onto the website. This post is one of the lower-effort posts since it doesn&rsquo;t require research!</p>
<h2 id="importing-mastodon-posts">Importing mastodon posts</h2>
<p>Mastodon posts are automatically downloaded and re-hosted on this website. This serves a few purposes:</p>
<ul>
<li>At the bottom of every blog post, the corresponding mastodon post is referenced. I hope this will be a good way to point readers who want to make a comment on the article or to give me a thumbs up.</li>
<li>Allows me to reference Mastodon posts inside of blog posts (without relying on an external service or iframes). I haven&rsquo;t used this yet, but it may come in handy.</li>
<li>It also just acts as my personal mastodon archive. Because I have this history, I don&rsquo;t need some other mechanism to back up my public Mastodon posts. I strongly believe in content ownership, which is the whole reason I don&rsquo;t use many of the existing blog publishing platforms except for syndication.</li>
</ul>
<h2 id="prompt-of-the-day">Prompt of the day</h2>
<p>I&rsquo;ve started posting <a href="https://kmcd.dev/prompts/">a daily writing prompt</a> every day on my Mastodon account. This is automatically posted from a combination of future-dated pages in Hugo, scheduled Github Actions and Echofeed. I feel like these prompts help me write SOMETHING every day. I typically just respond to the mastodon post. There&rsquo;s been an encouraging amount of participation from others with this. It was fun to come up with the first month&rsquo;s worth of questions. <a href="https://infosec.exchange/@sudorandom" rel="external">Follow me on Mastodon to participate in these prompts!</a></p>
<h2 id="rss">RSS</h2>
<p>I&rsquo;ve made some improvements to how RSS feeds are generated so that links and images use absolute paths in all cases, which allows you to see these images in your RSS feed if your reader supports that. It&rsquo;s strange how awful the &ldquo;out of the box&rdquo; experience is with Hugo and RSS. I feel like at least some of my issues should be handled automatically.</p>
<h2 id="small-refinements">Small Refinements</h2>
<p>I originally used the <a href="https://github.com/coolapso/hugo-theme-hello-4s3ti" rel="external">Hello Friend 4s3ti</a> theme for my blog. I&rsquo;ve slowly made small adjustments, moving more and more layouts away from the theme into the main blog section. At some point, I may copy over the rest of the resources and use a completely custom theme which will allow me to delete features I don&rsquo;t ever plan on using and more easily customize. But anyway, I like the style that I&rsquo;ve settled on. It fits me.</p>
<h3 id="projects-page">Projects Page</h3>
<p>I updated the projects page to be more&hellip; clear. The image gallery format just wasn&rsquo;t working to show what I worked on. I&rsquo;m not an artist and I should come to terms with that! See the page <a href="https://kmcd.dev/stuff/">here</a>.</p>
<h3 id="links-page">Links Page</h3>
<p>I have become a fan of the &ldquo;small web&rdquo; and I feel like link pages bring some of that small web feeling back. See mine, <a href="https://kmcd.dev/links/">here</a>.</p>
<h2 id="technologies">Technologies</h2>
<p>Some may be interested to know how this blog is built. The source code is available here on GitHub at <a href="https://github.com/sudorandom/kmcd.dev/" rel="external">sudorandom/kmcd.dev</a>. An enterprising reader could get sneak-peaks by watching this repo 👀</p>
<h3 id="cloudflare">Cloudflare</h3>
<p>As you may be able to tell from DNS, I use Cloudflare as a CDN, DNS, and firewall. Cloudflare has an incredibly generous free tier, which I use extensively.</p>
<h3 id="github-pages">GitHub Pages</h3>
<p>The actual website is hosted on Github Pages. GitHub also has a generous free tier here, but because of Cloudflare, I&rsquo;m not sure it actually gets a lot of non-cached traffic. I think I could easily switch to using <a href="https://www.cloudflare.com/developer-platform/r2/" rel="external">Cloudflare R2</a> if I needed to. The killer feature for GitHub pages is having easy integration with GitHub Actions, which I use to build and deploy the website. I simply push to my main branch and the process is handled from there. I also rely on GitHub Actions to publish scheduled content for me and to import Mastodon post updates for me.</p>
<h3 id="hugo">Hugo</h3>
<p>I use <a href="https://gohugo.io/" rel="external">Hugo</a> for my static website generator. I prefer it due to the Go templating style that I was already very familiar with along with the ease of use. With Hugo, I don&rsquo;t need to download 100 different npm packages to build my website. It does have its quirks, though and some things I do get frustrated about.</p>
<h3 id="echofeed">EchoFeed</h3>
<p><a href="https://echofeed.app/" rel="external">EchoFeed</a> is a new but welcome addition to my tech stack. This service consumes my RSS/Atom/jsonfeed feeds and makes corresponding posts to&hellip; somewhere. Currently, it supports Mastodon, bluesky, discord, Github, webmentions, webhooks, and a few others. Here&rsquo;s how I use the service:</p>
<ul>
<li>updating mastodon when there is a new post</li>
<li>updating mastodon with a new prompt of the day</li>
<li>updating the github repo with new mastodon posts from myself</li>
</ul>
<h2 id="until-next-time">Until Next Time</h2>
<p>I&rsquo;m always looking for ways to improve this blog, so let me know on Mastodon what you think! In the meantime, I&rsquo;m planning to work on the <a href="https://kmcd.dev/series/grpc-from-scratch/">gRPC From Scratch</a> series in the coming weeks. Thanks for reading!</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-plaintext" data-lang="plaintext"><span style="display:flex;"><span>01110011 01100101 01100101 00100000
</span></span><span style="display:flex;"><span>01111001 01101111 01110101 00100000
</span></span><span style="display:flex;"><span>01110011 01110000 01100001 01100011 01100101 00100000
</span></span><span style="display:flex;"><span>01100011 01101111 01110111 01100010 01101111 01111001
</span></span></code></pre></div>]]></content:encoded></item><item><title>gRPC From Scratch: Part 3 - Protobuf Encoding</title><link>https://kmcd.dev/posts/grpc-from-scratch-part-3/</link><pubDate>Tue, 07 May 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/grpc-from-scratch-part-3/</guid><description><![CDATA[ 
                <p> <img hspace="5" src="https://kmcd.dev/posts/grpc-from-scratch-part-3/cover_hu_bcc84d40218afdb0.webp" /> </p>
                
                Let&#39;s look under the hood of gRPC by getting into the weeds of protocol buffers.
                ]]></description><content:encoded><![CDATA[<p>In the last two parts, I showed how to make an extremely simple gRPC client and server that&hellip; kind-of works. But I punted on a topic last time that is pretty important: I used generated protobuf types and the Go protobuf library to do all of the heavy lifting of encoding and decoding protobufs for me. That ends today. I&rsquo;ll start by looking at the <a href="https://pkg.go.dev/google.golang.org/protobuf/encoding/protowire" rel="external"><code>protowire</code></a> library directly, which is a bit closer to what is actually happening on the wire. The library includes a fun disclaimer:</p>
<blockquote>
<p>For marshaling and unmarshaling entire protobuf messages, use the google.golang.org/protobuf/proto package instead.</p>
</blockquote>
<p>Am I going to listen to this solid advice? No! I want to know how this works! No reflection and no reliance on generated code. All of the code in this post are taken from unit tests <a href="https://github.com/sudorandom/kmcd.dev/blob/main/content/posts/2024/grpc-from-scratch-part-3/go/protowire_test.go" rel="external">available here</a> so feel free to download the tests and play around with it locally. Now, let&rsquo;s get started.</p>
<h2 id="wire-types">Wire Types</h2>
<p>I discuss in my <a href="https://kmcd.dev/posts/inspecting-protobuf-messages/">Inspecting Protobuf Messages</a> post that protobuf only has a small handful of types. Here they are again:</p>
<table>
  <thead>
      <tr>
          <th>ID</th>
          <th>Name</th>
          <th>Used for</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0</td>
          <td>VARINT</td>
          <td>int32, int64, uint32, uint64, sint32, sint64, bool, enum</td>
      </tr>
      <tr>
          <td>1</td>
          <td>I64</td>
          <td>fixed64, sfixed64, double</td>
      </tr>
      <tr>
          <td>2</td>
          <td>LEN</td>
          <td>string, bytes, embedded messages, packed repeated fields</td>
      </tr>
      <tr>
          <td>3</td>
          <td>SGROUP</td>
          <td>group start (deprecated)</td>
      </tr>
      <tr>
          <td>4</td>
          <td>EGROUP</td>
          <td>group end (deprecated)</td>
      </tr>
      <tr>
          <td>5</td>
          <td>I32</td>
          <td>fixed32, sfixed32, float</td>
      </tr>
  </tbody>
</table>
<p>For today we&rsquo;re only going to implement some of the <code>LEN</code> and <code>VARINT</code> wire types. Protobuf messages are a series of key-value pairs. Keys are &ldquo;field numbers&rdquo; and values are one of the types in the table above. To save space, protobuf decided to encode both the field number and wire type into a single byte. The lower three bits are the wire type and the other 5 (and maybe more, more on that later) are used for the field number. This is what the &ldquo;Tag-Length&rdquo; part looks like for field number <code>1</code> with the <code>VARINT</code> wire type:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-plaintext" data-lang="plaintext"><span style="display:flex;"><span>0000 1000
</span></span></code></pre></div><p>You can shift the three least significant bits off to get the protobuf wire type and the rest is used for the field number. It is at this point that the bitwise operators start becoming useful:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-plaintext" data-lang="plaintext"><span style="display:flex;"><span>(field_number &lt;&lt; 3) | wire_type
</span></span></code></pre></div><p>So&hellip; 3 bits for the wire type leaves room for 8 different options so protobuf has room for two more wire types before they have to break compatibility with older versions or add another byte to describe more types. And 5 bits for the field number which leaves room for&hellip;&hellip; <strong>32 different fields</strong>??? What?! You can&rsquo;t have a message that has more than 32 fields?! Why is no one talking about this <em>glaring</em> limitation where the number of fields is limited to a four-year-old&rsquo;s counting ability?! Well, obviously this is not true and, at this point, I am now forced to explain what protobuf refers to as <code>Base 128 Varints</code>.</p>
<h3 id="big-numbers">Big numbers</h3>
<p>In the previous example, we saw <code>VARINT</code> take a single byte. What does it look like when your number is too big? Where does the variableness part of <code>VARINT</code> come in? The protobuf encoding uses what it calls Base-128 Variable Integers in several places. &ldquo;Base 128&rdquo; means you can count to 127 before rolling over to the next &ldquo;digit&rdquo; (or in this case, byte). The most significant bit is used as a continuation bit, which is a signal that there&rsquo;s at least one more byte worth of data to complete this integer. Let&rsquo;s decode one for practice:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-plaintext" data-lang="plaintext"><span style="display:flex;"><span>11000000 11000100 00000111   // Original inputs.
</span></span><span style="display:flex;"><span> 1000000  1000100  0000111   // Drop continuation bits.
</span></span><span style="display:flex;"><span> 0000111  1000100  1000000   // Convert to big-endian.
</span></span><span style="display:flex;"><span> 000011110001001000000       // Concatenate.
</span></span><span style="display:flex;"><span> (1 × 2^16) + (1 × 2^15) +   // Convert binary to decimal
</span></span><span style="display:flex;"><span> (1 × 2^14) + (1 × 2^13) +   // because we&#39;re not a computer.
</span></span><span style="display:flex;"><span> (1 × 2^9) + (1 × 2^6)
</span></span><span style="display:flex;"><span> = 123456                    // Interpret as an unsigned 64-bit integer.
</span></span></code></pre></div><p>Next, I will show you some go code that can do this <code>VARINT</code> encoding process for us. Note that this code is actually in Go&rsquo;s <a href="https://pkg.go.dev/encoding/binary" rel="external">standard library</a> and makes reference to protocol-buffers directly <a href="https://pkg.go.dev/encoding/binary" rel="external">in the documentation</a>; (<a href="https://github.com/golang/go/blob/go1.22.2/src/encoding/binary/varint.go#L39-L47" rel="external">source</a>).</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// AppendUvarint appends the varint-encoded form of x,</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// as generated by [PutUvarint], to buf and returns the extended buffer.</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">AppendUvarint</span><span style="color:#eceff4">(</span>buf <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> x <span style="color:#81a1c1">uint64</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> x <span style="color:#81a1c1">&gt;=</span> <span style="color:#b48ead">0x80</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		buf <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>buf<span style="color:#eceff4">,</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>x<span style="color:#eceff4">)|</span><span style="color:#b48ead">0x80</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		x <span style="color:#81a1c1">&gt;&gt;=</span> <span style="color:#b48ead">7</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>buf<span style="color:#eceff4">,</span> <span style="color:#81a1c1">byte</span><span style="color:#eceff4">(</span>x<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>What&rsquo;s happening here? Let&rsquo;s break it down:</p>
<ul>
<li>The core logic is in a <code>for</code> loop that continues as long as x is greater than or equal to 128 (represented by <code>0x80</code> in hexadecimal). Why 128? That&rsquo;s the highest number (in hexadecimal) that you can count to only using 7 bits. Remember that the most significant bit (MSB) is used as a continuation bit so we can only use 7 out 8 of the available bits in each byte.
<ul>
<li>The next line appends the <code>uint64</code> argument truncated to a byte, so the last 8 bits. One of those bits isn&rsquo;t actually used because of the <code>|0x80</code> part of the line. This combines our extracted 7 bits with <code>0x80</code> to ensure that the continuation bit is set to true.</li>
<li>The next line uses the right shift operator to shift 7 bits off of our current <code>uint64</code> value because we&rsquo;ve &ldquo;dealt with&rdquo; these bits already. Next, we check the for-loop condition again until we get a number lower than <code>128</code>.</li>
</ul>
</li>
<li>Finally, we append the final byte as-is because we know that it&rsquo;s less than 128 which assures us that the MSB is not set, so we are done building this integer.</li>
</ul>
<p>Next, we have the decoding code for VARINT. That looks like this (<a href="https://github.com/golang/go/blob/go1.22.2/src/encoding/binary/varint.go#L69-L88" rel="external">source</a>):</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Uvarint decodes a uint64 from buf and returns that value and the</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// number of bytes read (&gt; 0). If an error occurred, the value is 0</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// and the number of bytes n is &lt;= 0 meaning:</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">//
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">//	n == 0: buf too small</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">//	n  &lt; 0: value larger than 64 bits (overflow)</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">//	        and -n is the number of bytes read</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">Uvarint</span><span style="color:#eceff4">(</span>buf <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">uint64</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">int</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> x <span style="color:#81a1c1">uint64</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> s <span style="color:#81a1c1">uint</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">for</span> i<span style="color:#eceff4">,</span> b <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1;font-weight:bold">range</span> buf <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#81a1c1">==</span> MaxVarintLen64 <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#616e87;font-style:italic">// Catch byte reads past MaxVarintLen64.</span>
</span></span><span style="display:flex;"><span>			<span style="color:#616e87;font-style:italic">// See issue https://golang.org/issues/41185</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">-</span><span style="color:#eceff4">(</span>i <span style="color:#81a1c1">+</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// overflow</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		<span style="color:#81a1c1;font-weight:bold">if</span> b <span style="color:#eceff4">&lt;</span> <span style="color:#b48ead">0x80</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">if</span> i <span style="color:#81a1c1">==</span> MaxVarintLen64<span style="color:#81a1c1">-</span><span style="color:#b48ead">1</span> <span style="color:#81a1c1">&amp;&amp;</span> b <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">1</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">-</span><span style="color:#eceff4">(</span>i <span style="color:#81a1c1">+</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">)</span> <span style="color:#616e87;font-style:italic">// overflow</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>			<span style="color:#81a1c1;font-weight:bold">return</span> x <span style="color:#eceff4">|</span> <span style="color:#81a1c1">uint64</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">)</span><span style="color:#81a1c1">&lt;&lt;</span>s<span style="color:#eceff4">,</span> i <span style="color:#81a1c1">+</span> <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>		x <span style="color:#81a1c1">|=</span> <span style="color:#81a1c1">uint64</span><span style="color:#eceff4">(</span>b<span style="color:#81a1c1">&amp;</span><span style="color:#b48ead">0x7f</span><span style="color:#eceff4">)</span> <span style="color:#81a1c1">&lt;&lt;</span> s
</span></span><span style="display:flex;"><span>		s <span style="color:#81a1c1">+=</span> <span style="color:#b48ead">7</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">0</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><ul>
<li>It loops through each byte:
<ul>
<li>Checks for overflow (reading past expected max size).</li>
<li>Handles the last byte (if the continuation bit is <code>0</code>):
<ul>
<li>Validates it and returns the decoded value.</li>
</ul>
</li>
<li>Processes continuation bytes:
<ul>
<li>Extracts data bits and combines them with the accumulated value, shifting them to their correct position. The expression <code>b&amp;0x7f</code> ensures that the continuation bit is not included in the result by using a bitwise AND with the current byte and <code>0x7f</code>, which looks like this in binary <code>01111111</code>.</li>
<li><code>s</code> keeps track of the total bits already processed. It increments by 7 because that&rsquo;s how many bits we can use for each byte because of the continuation bit.</li>
</ul>
</li>
</ul>
</li>
<li>If the loop finishes without a valid ending, it signals an error.</li>
</ul>
<p>As a short aside, you should probably know that the modern protobuf library doesn&rsquo;t use these functions from the <code>encoding/binary</code> standard library package. I suspect that they were initially used but the current <a href="https://pkg.go.dev/google.golang.org/protobuf/encoding/protowire" rel="external"><code>protowire</code></a> implementation doesn&rsquo;t use <code>for</code> loops at all because it has performed an optimization technique called <a href="https://en.wikipedia.org/wiki/Loop_unrolling" rel="external">loop unrolling</a>. Here&rsquo;s <a href="https://github.com/protocolbuffers/protobuf-go/blob/v1.33.0/encoding/protowire/wire.go#L184-L263" rel="external">AppendVarint</a> and <a href="https://github.com/protocolbuffers/protobuf-go/blob/v1.33.0/encoding/protowire/wire.go#L265-L367" rel="external">ConsumeVarint</a> that are now used in the modern protobuf library. That code looks insane but it is likely a bit faster than the version of the code that I just showed you.</p>
<p>As a test for comprehension of this last section, you should now understand that smaller numbers take up less room on the wire. That&rsquo;s true for all numeric types except for the fixed-length types: <code>I64</code> and <code>I32</code>. Encoding <code>20</code> in protobbuf takes a single byte on the wire but <code>1234</code> would take two bytes.</p>
<h2 id="integers">Integers</h2>
<p>Now that we know how the <code>VARINT</code> wire-type works we now have enough raw material to write some protobuf packets. Again, we need three things to make a message with a single field: Field number, wire type, and the encoded value. As mentioned earlier, the field number and wire type are merged into a single <code>VARINT</code> value using the formula: <code>(field_number &lt;&lt; 3) | wire_type</code>. This essentially means that the the least significant bits are reserved for the wire type and the rest are used for the field number, and we expand using the Base-128 varint method above if the field number needs more bits to be represented. Now, let&rsquo;s write a full field in protobuf! Here&rsquo;s how we encode a message with a single int32 field:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-plaintext" data-lang="plaintext"><span style="display:flex;"><span>1: 1234
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> buf <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span>
</span></span><span style="display:flex;"><span>buf <span style="color:#eceff4">=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">AppendVarint</span><span style="color:#eceff4">(</span>buf<span style="color:#eceff4">,</span> <span style="color:#81a1c1">uint64</span><span style="color:#eceff4">(</span><span style="color:#b48ead">1</span><span style="color:#81a1c1">&lt;&lt;</span><span style="color:#b48ead">3</span><span style="color:#eceff4">)|</span><span style="color:#81a1c1">uint64</span><span style="color:#eceff4">(</span><span style="color:#b48ead">0</span><span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>buf <span style="color:#eceff4">=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">AppendVarint</span><span style="color:#eceff4">(</span>buf<span style="color:#eceff4">,</span> <span style="color:#b48ead">1234</span><span style="color:#eceff4">)</span>
</span></span></code></pre></div><p>And&hellip; that&rsquo;s it. We&rsquo;ve fully encoded probably the simplest (non-empty) protobuf message. We can check that it works by running a test with the &ldquo;real&rdquo; protobuf library:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">TestEncodeRaw</span><span style="color:#eceff4">(</span>t <span style="color:#81a1c1">*</span>testing<span style="color:#eceff4">.</span>T<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">var</span> buf <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span>
</span></span><span style="display:flex;"><span>	buf <span style="color:#eceff4">=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">AppendVarint</span><span style="color:#eceff4">(</span>buf<span style="color:#eceff4">,</span> <span style="color:#81a1c1">uint64</span><span style="color:#eceff4">(</span><span style="color:#b48ead">1</span><span style="color:#81a1c1">&lt;&lt;</span><span style="color:#b48ead">3</span><span style="color:#eceff4">)|</span><span style="color:#81a1c1">uint64</span><span style="color:#eceff4">(</span><span style="color:#b48ead">0</span><span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	buf <span style="color:#eceff4">=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">AppendVarint</span><span style="color:#eceff4">(</span>buf<span style="color:#eceff4">,</span> <span style="color:#b48ead">1234</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	res <span style="color:#81a1c1">:=</span> gen<span style="color:#eceff4">.</span>TestMessage<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>	require<span style="color:#eceff4">.</span><span style="color:#88c0d0">NoError</span><span style="color:#eceff4">(</span>t<span style="color:#eceff4">,</span> proto<span style="color:#eceff4">.</span><span style="color:#88c0d0">Unmarshal</span><span style="color:#eceff4">(</span>buf<span style="color:#eceff4">,</span> <span style="color:#81a1c1">&amp;</span>res<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	assert<span style="color:#eceff4">.</span><span style="color:#88c0d0">Equal</span><span style="color:#eceff4">(</span>t<span style="color:#eceff4">,</span> <span style="color:#81a1c1">int32</span><span style="color:#eceff4">(</span><span style="color:#b48ead">1234</span><span style="color:#eceff4">),</span> res<span style="color:#eceff4">.</span>IntValue<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>By the way, these tests are using a protobuf file that looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">example</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">TestMessage</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#616e87;font-style:italic">// Basic types
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span>  <span style="color:#81a1c1">int32</span> int_value <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">uint64</span> uint_value <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">bool</span> bool_value <span style="color:#81a1c1">=</span> <span style="color:#b48ead">3</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> string_value <span style="color:#81a1c1">=</span> <span style="color:#b48ead">4</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#616e87;font-style:italic">// Repeated fields (varint encoding)
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span>  <span style="color:#81a1c1;font-weight:bold">repeated</span> <span style="color:#81a1c1">int32</span> repeated_int_value <span style="color:#81a1c1">=</span> <span style="color:#b48ead">10</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#616e87;font-style:italic">// Nested message
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span>  <span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">NestedMessage</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1">string</span> nested_string <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  NestedMessage nested_message <span style="color:#81a1c1">=</span> <span style="color:#b48ead">11</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>All of the field numbers and corresponding types in this article match up to this protobuf file.</p>
<p>Okay, let&rsquo;s show what it looks like to read this message:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>tagNumber<span style="color:#eceff4">,</span> protoType<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">ConsumeTag</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">...</span>
</span></span><span style="display:flex;"><span>b <span style="color:#eceff4">=</span> b<span style="color:#eceff4">[</span>n<span style="color:#eceff4">:]</span>
</span></span><span style="display:flex;"><span>i<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">ConsumeVarint</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">)</span>
</span></span></code></pre></div><p>Note that I omitted error handling and in a real scenario we need to check the <code>tagNumber</code> and <code>protoType</code> to decide how to read this field.</p>
<h2 id="stringsbyte-arrays">Strings/byte arrays</h2>
<p>Next, we&rsquo;re going to start with strings and byte arrays. These use the <code>LEN</code> wire type. This type is a little more complex than <code>VARINT</code>. It&rsquo;s composed of a <code>VARINT</code> that tells us the size of the <code>LEN</code> field followed by the actual content in bytes. For <code>LEN</code> this content can be a string, a byte array, an embedded message or packed repeated fields. Here&rsquo;s what a field looks like:</p>
<ul>
<li>Field Tag Byte (field number along with the field type, set to <code>2</code> for the <code>LEN</code> type)</li>
<li>Byte size of our content as a <code>VARINT</code></li>
<li>The actual content</li>
</ul>
<p>Encode:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> buf <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span>
</span></span><span style="display:flex;"><span>buf <span style="color:#eceff4">=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">AppendTag</span><span style="color:#eceff4">(</span>buf<span style="color:#eceff4">,</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">Number</span><span style="color:#eceff4">(</span><span style="color:#b48ead">4</span><span style="color:#eceff4">),</span> protowire<span style="color:#eceff4">.</span>BytesType<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>buf <span style="color:#eceff4">=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">AppendString</span><span style="color:#eceff4">(</span>buf<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;Hello World!&#34;</span><span style="color:#eceff4">)</span>
</span></span></code></pre></div><p>Decode:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>tagNumber<span style="color:#eceff4">,</span> protoType<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">ConsumeTag</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>b <span style="color:#eceff4">=</span> b<span style="color:#eceff4">[</span>n<span style="color:#eceff4">:]</span>
</span></span><span style="display:flex;"><span>i<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">ConsumeVarint</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">)</span>
</span></span></code></pre></div><h2 id="integer-arrays-packed">Integer Arrays (Packed)</h2>
<p>Packed repeated fields are a space-saving optimization for integer arrays. It&rsquo;s a feature that&rsquo;s enabled by default for most <code>repeated</code> scalar types when using the proto3 syntax. Instead of encoding each element individually as a separate field/value pair an entire array is packed into a single <code>LEN</code> type. Therefore it looks like the following for repeated <code>int32</code> types:</p>
<ul>
<li><code>VARINT</code> of our field number with the three least significant bits being reserved for field type for <code>LEN</code> which is <code>2</code>. This is the</li>
<li>The raw integer data, also encoded with VARINT for our <code>int32</code> type, one after another</li>
</ul>
<p>Here&rsquo;s an example of how to encode a packed repeated integer field:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>arr <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">int32</span><span style="color:#eceff4">{</span><span style="color:#b48ead">100002130</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">3</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">4</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">5</span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">var</span> buf<span style="color:#eceff4">,</span> buf2 <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span>
</span></span><span style="display:flex;"><span>buf <span style="color:#eceff4">=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">AppendVarint</span><span style="color:#eceff4">(</span>buf<span style="color:#eceff4">,</span> <span style="color:#81a1c1">uint64</span><span style="color:#eceff4">(</span><span style="color:#b48ead">10</span><span style="color:#81a1c1">&lt;&lt;</span><span style="color:#b48ead">3</span><span style="color:#eceff4">)|</span><span style="color:#81a1c1">uint64</span><span style="color:#eceff4">(</span><span style="color:#b48ead">2</span><span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">for</span> i <span style="color:#81a1c1">:=</span> <span style="color:#b48ead">0</span><span style="color:#eceff4">;</span> i <span style="color:#eceff4">&lt;</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>arr<span style="color:#eceff4">);</span> i<span style="color:#81a1c1">++</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	buf2 <span style="color:#eceff4">=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">AppendVarint</span><span style="color:#eceff4">(</span>buf2<span style="color:#eceff4">,</span> <span style="color:#81a1c1">uint64</span><span style="color:#eceff4">(</span>arr<span style="color:#eceff4">[</span>i<span style="color:#eceff4">]))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>buf <span style="color:#eceff4">=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">AppendVarint</span><span style="color:#eceff4">(</span>buf<span style="color:#eceff4">,</span> <span style="color:#81a1c1">uint64</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>buf2<span style="color:#eceff4">)))</span>
</span></span><span style="display:flex;"><span>buf <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>buf<span style="color:#eceff4">,</span> buf2<span style="color:#81a1c1">...</span><span style="color:#eceff4">)</span>
</span></span></code></pre></div><p>Notice that we are writing the list of <code>int32</code> values to a second buffer. I do this so that we can know the size of the encoded/packed <code>int32</code> values so that I can properly set the size for the encapsulating <code>LEN</code> wire type.</p>
<p>Decoding a packed repeated field is similar. First, you read the field tag (like always). Then we are using <code>protowire.ConsumeBytes</code> that reads the entire <code>LEN</code> value into a byte array. Then you read <code>varint</code> values until you exhaust the buffer. Remember that <code>varint</code> values have that continuation but so the <code>protowire.ConsumeVarint</code> function knows when to finish reading each <code>varint</code> value.</p>
<p>Here&rsquo;s what that looks like in code:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>tagNumber<span style="color:#eceff4">,</span> protoType<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">ConsumeTag</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">...</span>
</span></span><span style="display:flex;"><span>b <span style="color:#eceff4">=</span> b<span style="color:#eceff4">[</span>n<span style="color:#eceff4">:]</span>
</span></span><span style="display:flex;"><span>int32buf<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">ConsumeBytes</span><span style="color:#eceff4">(</span>b<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>b <span style="color:#eceff4">=</span> b<span style="color:#eceff4">[</span>n<span style="color:#eceff4">:]</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">...</span>
</span></span><span style="display:flex;"><span>res <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">int32</span><span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">for</span> <span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>int32buf<span style="color:#eceff4">)</span> <span style="color:#eceff4">&gt;</span> <span style="color:#b48ead">0</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	v<span style="color:#eceff4">,</span> n <span style="color:#81a1c1">:=</span> protowire<span style="color:#eceff4">.</span><span style="color:#88c0d0">ConsumeVarint</span><span style="color:#eceff4">(</span>int32buf<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	int32buf <span style="color:#eceff4">=</span> int32buf<span style="color:#eceff4">[</span>n<span style="color:#eceff4">:]</span>
</span></span><span style="display:flex;"><span>	res <span style="color:#eceff4">=</span> <span style="color:#81a1c1">append</span><span style="color:#eceff4">(</span>res<span style="color:#eceff4">,</span> <span style="color:#81a1c1">int32</span><span style="color:#eceff4">(</span>v<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><h2 id="conclusion">Conclusion</h2>
<p>In this part of the series, we&rsquo;ve taken a deep dive into the world of manual protobuf encoding. We explored a few wire types and the primitives that they are built on, like Base-128 varints, and built functions to encode and decode basic protobuf data types. While for most use cases, you&rsquo;ll probably want to leverage the efficiency and convenience of generated code and libraries like <code>golang/protobuf</code>, understanding how to manually encode these messages can be a valuable asset for debugging, creating a protobuf library implementation in your favorite new language or for possibly creating your own binary encoding that&rsquo;s better in some way than protobuf.</p>
<p>In the next part of the series, we will put more pieces together with a layer on top of our encoding code and integrate this protobuf library into the client and server that we made in parts 1 and 2. Build a real gRPC client and server that uses protobufs for data exchange!</p>
]]></content:encoded></item><item><title>Tracking the Wins</title><link>https://kmcd.dev/posts/tracking-the-wins/</link><pubDate>Tue, 30 Apr 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/tracking-the-wins/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/tracking-the-wins/cover_hu_840d211415a7c25.webp" /> &lt;/p>
                
                Stay motivated and avoid burnout
                </description><content:encoded><![CDATA[<p>Mental health is hugely important for software engineers. When I first started coding, the thrill of solving problems and getting that hit of dopamine from the instant feedback loop was incredible. It&rsquo;s easy to forget those initial challenges and accomplishments as you progress, though, and problems that used to look like unscalable walls turn into speed bumps. That&rsquo;s why I believe regularly reflecting on your work is so valuable. Otherwise, you might find yourself dwelling on the struggles and forgetting the successes you&rsquo;ve achieved along the way.</p>
<h2 id="writing-down-the-wins">Writing down the wins</h2>
<p>Here&rsquo;s how I track my progress: Every week or two, I set aside some time to quickly <strong><code>document my achievements</code></strong>, even small ones. I keep it concise to avoid procrastination. I have some anxiety around tasks with too many unknowns, so a simple, mechanical process works best for me. I&rsquo;m also a very forgetful person, so I usually have to review artifacts created by the work that I do, like following digital breadcrumbs that I leave around. Here&rsquo;s a list of where I might find these things:</p>
<ul>
<li>issues I&rsquo;ve worked on</li>
<li>pull requests</li>
<li>emails</li>
<li>edits to documentation</li>
<li>slack messages</li>
</ul>
<p>The more concrete evidence, the better, but some achievements don&rsquo;t leave clear evidence so be sure to capture interesting anecdotes as they happen. Anything that has allowed me to progress forward should be highlighted, even if it is a result of compromise or realizing that you made a mistake.</p>
<p>I also apply a similar strategy to my personal life. Did I stay on top of my Danish lessons and language learning apps? Did I have periods of being ill during this period? Was I physically active, by cycling, exercising, or simply taking the stairs? Tracking these things helps me see the broader picture.</p>
<h2 id="quarterly-reviews">Quarterly reviews</h2>
<p>To pair with the weekly tracking. <strong><code>I conduct a more in-depth reflection every 3 months</code></strong>. Here, I add my thoughts to these accomplishments. I create a narrative of the past quarter, encompassing both highs and lows, as well as the inevitable &ldquo;neutral&rdquo; experiences. Three months seems like a good timeframe to identify any significant shifts in my attitude or goals. It allows me to assess my progress on past goals and set new ones for the future. I believe this time frame is <em>MUCH</em> better than the yearly cycle of New Year resolutions. Twelve months is way too long. One month is usually not enough to make good progress on goals or to establish habits. Three months seems just right to me. So here&rsquo;s the list of what might appear on a quarterly review:</p>
<ul>
<li>A &ldquo;narrative&rdquo; of the last three months using concrete examples from my journal to back it up.</li>
<li>Report on the progress of previous goals.</li>
<li>Create new goals or revise the older goals if needed.</li>
<li>Pictures and video of important moments.</li>
<li>Reflect on big family budget items and see if they align with my priorities in life.</li>
</ul>
<h2 id="yearly-themes">Yearly themes</h2>
<p>Yes, <a href="https://www.relay.fm/cortex" rel="external">I am a Cortexan</a> and podcast episodes outlining <a href="https://www.youtube.com/watch?v=cXexYmOHkas" rel="external">yearly themes</a> are among my favorites. Yearly themes give a framework for me to push myself to do things I want to do while allowing me to adjust (every quarter) exactly what it means to accomplish my theme. Sometimes life throws additional challenges. For example, it&rsquo;s silly to fail at achieving a goal of &ldquo;running a mile every day&rdquo; if you break your leg.</p>
<p>For me, this year is <strong>&ldquo;The Year of Building&rdquo;</strong>. Here&rsquo;s what I wrote about my theme shortly before the year began:</p>
<blockquote>
<p>I want to build some cool things. I want to make some useful open source projects. I want to implement some neat solutions at work and actually prove them out. My follow-through sometimes lacks when I lose interest in things. I want to make more cool blog posts.</p>
<p>I also want to build on my Danish by continuing my Danish classes, self study with and using it more &ldquo;in the wild&rdquo;.</p>
</blockquote>
<p>I think I&rsquo;m doing a good job with this one. You may notice though that the latest batch of blog posts didn&rsquo;t start in January. I &ldquo;slacked&rdquo; on this goal for over a month but in my theme framework, it&rsquo;s fine because I was ramping up my Danish classes again. My yearly theme allows me a lot of grace on the day-week-month scale while hopefully keeping me honest on the yearly scale and allowing for adjustment of individual goals to match reality.</p>
<h2 id="try-it-yourself">Try it yourself</h2>
<p>Journaling and goal-setting are extremely personal things so your process may vary a lot from mine. But one thing is likely true of everyone: by keeping track of your wins, both big and small, you can maintain motivation and see your progress over time. Give it a try and see how it impacts your software engineering (and life) journey!</p>
]]></content:encoded></item><item><title>Visualizing the Internet (2024)</title><link>https://kmcd.dev/posts/internet-map-2024/</link><pubDate>Tue, 23 Apr 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/internet-map-2024/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/internet-map-2024/cover_hu_8c82ca350b1151a0.webp" /> &lt;/p>
                
                Map of the Internet including undersea cables and internet exchanges.
                </description><content:encoded><![CDATA[<p>I recently updated my <a href="https://kmcd.dev/posts/internet-map-2023/">Map of the Internet visualization</a> that shows all of the undersea Internet cables that run along the bottom of the oceans and seas and the Internet exchange points that offer peering. Together these show a pretty good view of the physical aspects of the internet. This time, I generated a video that shows the evolution of our current internet along with some interesting stats from each year. Enjoy:</p>
<h2 id="video">Video</h2>
<p><div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/dZDSUY-vBgs?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<a href="https://www.youtube.com/watch?v=dZDSUY-vBgs" rel="external">YouTube: Internet Map (2010-2024)</a></p>
<h2 id="still-images">Still images</h2>
<p>I, of course, also generated new versions of the static maps. Below is an overview of the entire world. Click on the image below to zoom/pan the full-resolution version or click the link under the image to download it:</p>
<p><figure><a href="https://kmcd.dev/posts/internet-map-2024/the-internet-map.svg"
            
             data-description="This map shows the locations of undersea cables and internet exchanges around the world."class="spotlight"
            data-preload="true"
            data-download="true"
            data-progress="true"
            data-infinite="true"
            data-spinner="true"
            data-autofit="true"
            data-fit="cover">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2024/the-internet-map_hu_2f5df267ec7ee34c.webp"
         alt="Map of the Internet"/>
    </a>
</figure>

<strong><a href="https://kmcd.dev/posts/internet-map-2024/the-internet-map.svg">Click here for full resolution image (warning, it&rsquo;s big)</a></strong></p>
<p>So what are you looking at? The lines are submarine cables and the circles are cities with Internet Exchange Points (IXPs).</p>
<h2 id="submarine-cables">Submarine Cables</h2>
<p>The squiggly lines on the map are <a href="https://en.wikipedia.org/wiki/Submarine_communications_cable" rel="external">submarine communications cables</a>. Submarine cables are truly a technological wonder. They carry vast amounts of data across the world&rsquo;s largest oceans. Yes, they just lie at the bottom of our oceans. Yes, it is crazy. From route selection, cable manufacturing, and <a href="https://en.wikipedia.org/wiki/Cable_layer" rel="external">specialized cable-laying ships</a>; it&rsquo;s all incredible. Even more normal aspects of fiber optic cables like having <a href="https://hackaday.com/2023/08/08/under-the-sea-optical-repeaters-for-submarine-cables/" rel="external">in-line amplifiers</a> become far more difficult when you cannot drive a truck out to fix something. You can&rsquo;t drive a truck to the bottom of the ocean. Well, you can but you won&rsquo;t be much use once you get there and good luck doing it more than once. Anyway&hellip; from <a href="https://en.wikipedia.org/wiki/Autonomous_underwater_vehicle" rel="external">amazing robots</a> that can locate and assist in bringing cables to the surface to <a href="https://www.youtube.com/watch?v=OKS-Hp7q-44" rel="external">in-water fiber optic repair habitats</a> every bit of submarine cable deployment and operations is extreme and fascinating.</p>
<p>Laying and operating submarine cables is a complex and expensive undertaking, but it is vital for our interconnected world. Note how <em>few</em> of these cables exist in total. They connect our content digitally and culturally but there are only <strong>549</strong> active submarine cables right now in the world. That truly is an extremely small amount but you should note that many more terrestrial cables connect on land. Too many for us to map or know about. So instead, I used data about internet exchange points to show the scale of the internet on land.</p>
<h2 id="internet-exchange-points">Internet Exchange Points</h2>
<p>The circles in the maps above are all <a href="https://www.cloudflare.com/learning/cdn/glossary/internet-exchange-point-ixp/" rel="external">buildings called &ldquo;Internet Exchange Points&rdquo;</a>. These buildings (<em>and sometimes closets</em>) are specialized data centers that have a uniquely large number of fiber connections from surrounding data centers, other internet exchanges and long-haul cables. These buildings are &ldquo;meeting points&rdquo; for Internet service providers (ISPs), cloud providers, content delivery networks (CDNs), and large enterprises like IBM and Apple and many could say that it is &ldquo;where&rdquo; the Internet lives. This is where networks interconnect or &ldquo;peer&rdquo;. Many of these Internet exchanges charge a monthly fee to participate in the exchange but who everyone peers with and the financial details around the arrangement is up to each company. Some companies like Apple or Google will peer with anyone because it brings Google devices closer to Google services and reduces latency and cost while improving reliability. Traffic served through peering, for Google, is traffic that they do not have to pay backbone providers for.</p>
<p>These exchanges usually have a &ldquo;gravity&rdquo; to them. It&rsquo;s more valuable to be in an exchange where there are already many other networks present. However, we need to remember to avoid putting all of our eggs into a single basket. Exchanges that are too large create a single point of failure, so large internet-connected cities need to have several of these exchanges.</p>
<h2 id="looking-at-each-region">Looking at each region</h2>
<p>Okay, let&rsquo;s look a bit closer at each region. In terms of infrastructure, you can see which areas have more investments.</p>
<div class="container">
  <pre class="mermaid">pie title Peering By Region
    "Europe" : 1390
    "North America" : 371
    "South America" : 236
    "Africa" : 47.3
    "Asia" : 308
    "Oceania" : 44.7
  </pre>
</div>
<h3 id="europe">Europe</h3>
<figure><a href="https://kmcd.dev/posts/internet-map-2024/the-internet-map-eu.png"
            
             data-description="Europe&#39;s internet peering cities and undersea cables."class="spotlight"
            data-preload="true"
            data-download="true"
            data-progress="true"
            data-infinite="true"
            data-spinner="true"
            data-autofit="true"
            data-fit="cover">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2024/the-internet-map-eu_hu_38102531491461be.png"
         alt="Europe"/>
    </a>
</figure>

<p>Europe reigns supreme in peering traffic, boasting a staggering 1.39 petabits of advertised peering bandwidth. This dominance can be attributed to several factors:</p>
<ul>
<li>European nations tend to have well-developed economies that prioritize high-speed internet infrastructure. This translates to greater investment in network backbones and internet exchange points (IXPs).</li>
<li>Europe has a dense network of IXPs, particularly in major cities like Frankfurt, Amsterdam, and London. These hubs facilitate efficient peering between numerous networks, reducing reliance on expensive long-haul transit.</li>
<li>Europe has a regulatory environment that encourages open peering practices. This fosters competition and innovation among internet service providers (ISPs), ultimately benefiting end users with lower costs and improved performance.</li>
</ul>
<h3 id="north-america">North America</h3>
<figure><a href="https://kmcd.dev/posts/internet-map-2024/the-internet-map-na.png"
            
             data-description="North America&#39;s internet peering cities and undersea cables."class="spotlight"
            data-preload="true"
            data-download="true"
            data-progress="true"
            data-infinite="true"
            data-spinner="true"
            data-autofit="true"
            data-fit="cover">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2024/the-internet-map-na_hu_5b176c7a194647f1.png"
         alt="North America"/>
    </a>
</figure>

<p>North America follows closely behind Europe with a respectable 371 terabits of peering traffic. Similar to Europe, factors like strong economies, a well-developed internet infrastructure, and the presence of major IXPs contribute to this robust peering ecosystem. However, North America&rsquo;s peering traffic might be slightly lower due to a larger geographical footprint compared to Europe.</p>
<p>I find it interesting that there are two submarine cables planned for 2024 (and two more in 2026) going to Myrtle Beach in South Carolina. It might be a decent time to start an internet exchange around that area since there&rsquo;s a good amount of submarine cable capacity going through that area now. This could improve peering options and potentially lower latency for users in the southeastern United States</p>
<h3 id="asia">Asia</h3>
<figure><a href="https://kmcd.dev/posts/internet-map-2024/the-internet-map-apac.png"
            
             data-description="Asia&#39;s internet peering cities and undersea cables."class="spotlight"
            data-preload="true"
            data-download="true"
            data-progress="true"
            data-infinite="true"
            data-spinner="true"
            data-autofit="true"
            data-fit="cover">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2024/the-internet-map-apac_hu_562208af0c757e60.png"
         alt="Asia"/>
    </a>
</figure>

<p>Asia exhibits a significant peering presence with 308 terabits of traffic. The region&rsquo;s economic growth and increasing internet penetration are driving forces behind this. But it&rsquo;s important to note:</p>
<ul>
<li>There&rsquo;s a significant disparity in development levels across Asian countries. While developed nations like Singapore and Japan boast strong peering infrastructure, others might lag behind.</li>
<li>Government Regulations: Some Asian governments might have stricter regulations on internet traffic, potentially impacting peering arrangements between ISPs.</li>
</ul>
<p>You may be trying to remember what the island is in the center-right of this picture that many things appear to connect to. That, my friends, is <a href="https://en.wikipedia.org/wiki/Guam" rel="external">Guam</a>, an unincorporated territory of the United States.</p>
<h3 id="africasouth-america">Africa/South America</h3>
<figure><a href="https://kmcd.dev/posts/internet-map-2024/the-internet-map-af.png"
            
             data-description="Africas&#39;s internet peering cities and undersea cables."class="spotlight"
            data-preload="true"
            data-download="true"
            data-progress="true"
            data-infinite="true"
            data-spinner="true"
            data-autofit="true"
            data-fit="cover">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2024/the-internet-map-af_hu_25504eacab59e365.png"
         alt="Africa"/>
    </a>
</figure>

<p>South America and Africa have the lowest peering traffic at 236 terabits and 47.3 terabits, respectively. This can be attributed to:</p>
<ul>
<li>These regions are still in the development stages when it comes to internet infrastructure. Lower investments in IXPs and network backbones can limit peering opportunities.</li>
<li>The number of IXPs in these regions might be lower compared to Europe and North America.</li>
</ul>
<figure><a href="https://kmcd.dev/posts/internet-map-2024/the-internet-map-sa.png"
            
             data-description="South America&#39;s internet peering cities and undersea cables."class="spotlight"
            data-preload="true"
            data-download="true"
            data-progress="true"
            data-infinite="true"
            data-spinner="true"
            data-autofit="true"
            data-fit="cover">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2024/the-internet-map-sa_hu_49df7ef7dd3e9b41.png"
         alt="South America"/>
    </a>
</figure>

<p>For South America there seems to be a geographically dominant peering spot: Buenos Aires in Argentina.</p>
<h2 id="how-its-made">How it&rsquo;s made</h2>
<p>The images are generated the same way I did <a href="https://kmcd.dev/posts/internet-map-2023/">in the last version</a>, so refer to that article for more context. Generating the video, however, required a little bit more technology. I primarily used a tool called <a href="https://github.com/tungs/timecut" rel="external">timecut</a> that records the SVG animations for me and encodes it as an mp4 file using <code>ffmpeg</code>. I used another <code>ffmpeg</code> call to mix in the audio song. By the way, I used some trial and error to figure out that the tempo of the song is <code>110bpm</code>. To find out how long to sleep before changing to the next frame I did some simple math&hellip; but I also double-checked it with <a href="https://sengpielaudio.com/calculator-bpmtempotime.htm" rel="external">this nifty website</a> that shows how long in milliseconds there are between between each beat with different tempos. It may have been easier to do this &ldquo;manually&rdquo; with video editing software but I enjoy the fact that I can generate the entire video from the command line. Because the fps is so low the 4k video file that I generate only takes up 18MB.</p>
<p>Here&rsquo;s the updated pipeline:</p>
<div class="container">
  <pre class="mermaid">graph LR
    submarinecables[Submarine Cable Database] --> golang[Go Script]
    peeringdb[PeeringDB] --> golang[Go Script]
    geocities[Geolocation Database] --> golang[Go Script]
    golang[Go Script] --> node[Node JS Script]
    node[Node JS Script] --> svg[SVG]
    node[Node JS Script] --> animated-svg[Animated SVG]
    svg[SVG] --> convert[ImageMagick Convert] --> png[PNG]
    svg[SVG] --> morgify[ImageMagick Morgify] --> jpg[Displate JPG]
    animated-svg[Animated SVG] --> timecut[Timecut/ffmpeg] --> mp4[MP4]
    style svg stroke:#f66,stroke-width:2px,stroke-dasharray: 5, 5;
    style animated-svg stroke:#f66,stroke-width:2px,stroke-dasharray: 5, 5;
    style jpg stroke:#f66,stroke-width:2px,stroke-dasharray: 5, 5;
    style png stroke:#f66,stroke-width:2px,stroke-dasharray: 5, 5;
    style mp4 stroke:#f66,stroke-width:2px,stroke-dasharray: 5, 5;
  </pre>
</div>
<h3 id="closing-thoughts">Closing Thoughts</h3>
<p>This visualization provides a fascinating glimpse into the physical infrastructure that underpins the Internet. As our reliance on the internet continues to grow, so too will the need for robust and resilient network infrastructure. By understanding the physical underpinnings of the internet, we can appreciate its ingenuity and work towards ensuring its continued growth and accessibility for everyone.</p>
<p>References:</p>
<ul>
<li>Github: <a href="https://github.com/sudorandom/submarine-cable-map" rel="external">https://github.com/sudorandom/submarine-cable-map</a></li>
<li>PeeringDB: <a href="https://www.peeringdb.com/" rel="external">https://www.peeringdb.com/</a></li>
<li>Simple Maps (Geolocation Database): <a href="https://simplemaps.com/data/world-cities" rel="external">https://simplemaps.com/data/world-cities</a></li>
<li>Submarine Cable Map: <a href="https://www.submarinecablemap.com" rel="external">https://www.submarinecablemap.com</a></li>
</ul>
]]></content:encoded></item><item><title>Making Greppable Code</title><link>https://kmcd.dev/posts/greppable/</link><pubDate>Tue, 16 Apr 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/greppable/</guid><description><![CDATA[ 
                <p> <img hspace="5" src="https://kmcd.dev/posts/greppable/cover_hu_29629b8c61f9e76e.webp" /> </p>
                
                Meaningful names help make code searchable so think twice before naming everything &#39;entity&#39;
                ]]></description><content:encoded><![CDATA[<p>Have you ever felt like you&rsquo;re sifting through lines of code, desperately searching for that elusive function or variable? Fear not, for there&rsquo;s a way to make your code more discoverable: <strong>greppable naming conventions</strong>.</p>
<h3 id="what-is-greppability">What is Greppability?</h3>
<p>Drawing inspiration from the powerful <a href="https://www.gnu.org/software/grep/manual/grep.html" rel="external">Linux command <code>grep</code></a> used for text searching, greppable code refers to code that&rsquo;s easy to search through using clear and meaningful names. Just like <code>grep</code> helps you quickly find specific text within a file, greppable names allow you to efficiently locate relevant code sections by searching for terms that reflect their purpose.</p>
<h3 id="crafting-greppable-names">Crafting Greppable Names</h3>
<p>Here are some key principles to follow:</p>
<ul>
<li><strong>Specificity:</strong> Ditch generic names like <code>processData()</code> or <code>utils</code>. Opt for terms that reflect the specific content (<code>cleanAndValidateOrderData()</code>, <code>orderId</code>).</li>
<li><strong>Priority:</strong> It is far more important for filenames, class names and methods to have specific names than it is for variables inside of functions. Variables inside of functions already have a lot of implied context, like the filename, class (if your language uses classes) and the function that the variable appears in. All of that extra context should be greppable but the variables probably don&rsquo;t need to be.</li>
<li><strong>Consistency:</strong> Maintain a consistent naming style throughout your codebase (e.g. camelCase or snake_case) and always use the &lsquo;standard&rsquo; for your language or framework.</li>
<li><strong>Verbs when naming methods:</strong> Methods describe actions. Use verbs at the beginning of method names to convey their purpose (<code>calculateOrderTotal()</code>, <code>updateCustomerRecord()</code>).</li>
<li><strong>Abbreviations with Caution:</strong> Use abbreviations sparingly and only for widely recognized terms (e.g. <code>HTTP</code>, <code>XML</code>). Overuse can hinder readability. If your variable names look like 2010s-era tech startups, you&rsquo;re probably doing it wrong.</li>
<li><strong>Dynamic Dispatch:</strong> Doing some more complex programming patterns like dynamic dispatching can greatly hurt the ability for the code to be greppable. It can also add indirection so be sure you use these methods sparingly and add more documentation where needed to help lost souls who are tracing code through the codebase.</li>
</ul>
<h3 id="the-search-advantage-of-grpc">The Search Advantage of gRPC</h3>
<p>Here&rsquo;s where things get interesting for APIs. Compared to RESTful APIs with their predefined resource structures, gRPC (and other RPC-based APIs) offer greater flexibility in naming methods. This freedom allows you to leverage greppable naming conventions to their full potential.</p>
<p>For instance, in a RESTful API, an endpoint for updating a user profile might be named <code>/users/:id</code>. While functional, this doesn&rsquo;t explicitly convey the action performed. In a gRPC API, however, you could have a method named <code>UpdateUserProfile(UpdateRequest request)</code>, which clearly describes the purpose and is much easier to search for in the code.</p>
<p><strong>The Greppability Benefit</strong></p>
<p>Meaningful names not only make code easier to understand but also significantly reduce maintenance time. When you (or another developer) revisit your codebase later, clear names can make traversing the codebase much nicer. I think the term &ldquo;greppability&rdquo; has a clear meaning and can come up in many contexts: in code, data, documentation, etc. Do you think this a good word for a developer to have in their vocabulary?</p>
]]></content:encoded></item><item><title>Building APIs with Contracts</title><link>https://kmcd.dev/posts/api-contracts/</link><pubDate>Tue, 09 Apr 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/api-contracts/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/api-contracts/cover_hu_a5b854d92e996d16.webp" /> &lt;/p>
                
                Building for Scale: Why contract-based APIs are the future.
                </description><content:encoded><![CDATA[<p>In today&rsquo;s interconnected world, APIs (Application Programming Interfaces) are the glue that connects computers. They allow different applications to talk to each other, share data, and perform actions. However, traditional methods of creating APIs can lead to challenges, especially when dealing with versioning changes and integrating complex systems. This is where <strong>contract-based APIs</strong> come in, offering a more robust and reliable approach and taming some of the wildness that exists on the web.</p>
<h3 id="the-power-of-pre-defined-api-contracts">The Power of Pre-defined API Contracts</h3>
<p>Imagine building a house without a blueprint. It would be chaotic and prone to errors. A contract-based API is like a detailed blueprint for communication between applications. It defines exactly what data can be exchanged, in what format, and what actions can be performed. This pre-defined agreement unlocks several advantages for developers and applications:</p>
<ul>
<li><strong>Improved Developer Experience:</strong>
<ul>
<li>Developers on both sides (client and server) have a clear understanding of what&rsquo;s expected, making integration smoother.</li>
<li><strong>Automated Documentation:</strong> Contracts serve as self-documenting artifacts, reducing the need for manual documentation creation and maintenance. This saves development time and ensures documentation stays in sync with the actual API implementation.</li>
</ul>
</li>
<li><strong>Reduced Errors:</strong>
<ul>
<li>Mismatched data formats or API changes become less likely, leading to fewer bugs and headaches. Contracts act as a validation layer, catching potential issues early in the development process.</li>
</ul>
</li>
<li><strong>Easier Integration:</strong>
<ul>
<li>Contracts act as a single source of truth, simplifying the process of connecting different services.  Developers can quickly understand how to interact with the API without extensive back-and-forth communication.</li>
</ul>
</li>
<li><strong>Streamlined Development:</strong>
<ul>
<li>Contract-based APIs often enable tools to automatically generate code for both client and server implementations based on the defined contracts. This eliminates manual coding and boilerplate, allowing developers to focus on core functionalities.</li>
</ul>
</li>
</ul>
<p>By leveraging pre-defined contracts, you can build more robust, reliable, and maintainable APIs that streamline development efforts and improve overall communication within your application ecosystem.</p>
<h3 id="protobuf-the-language-of-apis">Protobuf: The Language of APIs</h3>
<p>In my world, the foundation of many contract-based APIs lies in <a href="https://protobuf.dev/" rel="external"><strong>Protocol Buffers (protobuf)</strong></a>. It&rsquo;s a language-neutral data format specifically designed for structured messages exchanged between applications. Protobuf offers several advantages:</p>
<ul>
<li><strong>Smaller Message Sizes:</strong> Protobuf messages are compact and efficient, leading to faster transmission and reduced bandwidth usage.</li>
<li><strong>Faster Parsing:</strong> Parsing protobuf messages is significantly faster compared to traditional formats like JSON or XML.</li>
<li><strong>Cross-language Compatibility:</strong> Protobuf definitions are language-agnostic. Code for interacting with the API can be generated for many programming languages.</li>
</ul>
<p>Defining service contracts in protobuf involves creating <code>.proto</code> files. These files specify the structure of messages (data fields and types) and define the service methods (requests and responses) that an API offers.</p>
<p>Here&rsquo;s a basic example of a <code>.proto</code> file defining messages for a user and an address:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">User</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">int32</span> id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> email <span style="color:#81a1c1">=</span> <span style="color:#b48ead">3</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  Address address <span style="color:#81a1c1">=</span> <span style="color:#b48ead">4</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">Address</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> street <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> city <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> state <span style="color:#81a1c1">=</span> <span style="color:#b48ead">3</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> zip <span style="color:#81a1c1">=</span> <span style="color:#b48ead">4</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>In this example, the <code>User</code> message has fields for name, ID, email, and an <code>Address</code> message. The <code>Address</code> message itself has fields for street, city, state, and zip code. These defined message structures ensure consistent data exchange between applications.</p>
<h3 id="grpc-building-apis-on-a-solid-foundation">gRPC: Building APIs on a Solid Foundation</h3>
<p><strong>gRPC (gRPC Remote Procedure Call)</strong> is a high-performance framework that builds upon protobuf&rsquo;s strengths. It provides a powerful way to implement remote procedure calls, allowing applications to interact using clients generated for each language using (usually) types and semantics that make sense to the language. Here&rsquo;s how gRPC leverages protobuf:</p>
<ul>
<li><strong>Protobuf Messages for Communication:</strong> gRPC uses protobuf messages to define the request and response data exchanged between client and server.</li>
<li><strong>Generated Code for Seamless Interaction:</strong> gRPC automatically generates server and client stubs (boilerplate code) from the <code>.proto</code> files. This eliminates manual coding and ensures consistency between client and server implementations.</li>
</ul>
<h4 id="introducing-services-and-requestresponse-types-with-grpc">Introducing Services and Request/Response Types with gRPC</h4>
<p>Now let&rsquo;s expand on the concept of services within a <code>.proto</code> file. We can define a service called <code>UserService</code> with methods for user management:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">service</span> UserService <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">rpc</span> CreateUser<span style="color:#eceff4">(</span>CreateUserRequest<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span> <span style="color:#eceff4">(</span>User<span style="color:#eceff4">)</span> <span style="color:#eceff4">{}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">rpc</span> GetUser<span style="color:#eceff4">(</span>GetUserRequest<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span> <span style="color:#eceff4">(</span>User<span style="color:#eceff4">)</span> <span style="color:#eceff4">{}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">CreateUserRequest</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  User user <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GetUserRequest</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">int32</span> id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>This example defines a <code>UserService</code> with two methods: <code>CreateUser</code> and <code>GetUser</code>. Each method takes a specific request message and returns a response message. The <code>CreateUserRequest</code> message contains a <code>User</code> object to be created, while the <code>GetUserRequest</code> specifies the user ID to retrieve. This clear separation of request and response types further enhances the contract between client and server. You should also notice just how clear the intention is for the methods of UserServer. A reader of this spec doesn&rsquo;t have to do a mapping of extremely vague words like &ldquo;POST&rdquo; to more understandable actions like &ldquo;create&rdquo;. Also notice how the <code>CreateUser</code> and <code>GetUser</code> methods are <a href="https://en.wiktionary.org/wiki/greppable" rel="external">greppable</a>, making it trivial to locate uses of these RPCs, even across several repositories.</p>
<h2 id="alternatives">Alternatives</h2>
<p>While Protobuf and gRPC are a powerful duo, there are other contract-based API solutions to consider:</p>
<ul>
<li><a href="https://www.openapis.org/" rel="external"><strong>OpenAPI (Swagger)</strong></a>: This is a popular specification for defining RESTful APIs, offering a standardized way to document and interact with web services.</li>
<li><a href="https://thrift.apache.org/" rel="external"><strong>Thrift</strong></a>: Similar to protobuf, Thrift is a language-neutral protocol for defining service contracts. It supports various RPC protocols beyond gRPC.</li>
<li><a href="https://avro.apache.org/" rel="external"><strong>Avro</strong></a>: This JSON-like data format uses schemas to ensure reliable data exchange. It&rsquo;s often used with Apache Kafka for streaming data pipelines.</li>
</ul>
<p>The choice between these options depends on your specific needs. gRPC and protobuf excel for high-performance, RPC-based communication, while OpenAPI is better suited for existing RESTful APIs. Thrift is also there. Avro has some dynamic typing features and has self-describing messages but is also slower for the same reason.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Contract-based APIs offer a significant advantage in building robust and scalable communication between applications. protobuf and gRPC provide a powerful combination for defining clear contracts and generating efficient code. By leveraging these technologies, you can streamline API development, improve developer experience, and ensure seamless integration within.</p>
]]></content:encoded></item><item><title>Dropping Unknown Fields in ConnectRPC</title><link>https://kmcd.dev/posts/connectrpc-dropping-unknown-fields/</link><pubDate>Tue, 02 Apr 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/connectrpc-dropping-unknown-fields/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/connectrpc-dropping-unknown-fields/cover_hu_6b091745410a087d.webp" /> &lt;/p>
                
                Learn how to drop unknown fields in ConnectRPC to enhance the security of your gRPC services exposed to the internet.
                </description><content:encoded><![CDATA[<p>gRPC, with its focus on performance and language neutrality, remains a popular choice for building microservices and APIs. But when exposing your gRPC service to the internet, there are a few security considerations to account for. Protobuf, the serialization format often used with gRPC, offers various encoding options that can significantly impact your service&rsquo;s security posture.</p>
<p>One crucial optimization for internet-facing gRPC services is customizing the behavior towards <strong>unknown fields</strong>. I&rsquo;ve talked about <a href="https://kmcd.dev/posts/protobuf-unknown-fields/">unknown fields in a previous post</a>, so read that one if unknown fields are still a mystery to you and then come back here. By default, protobuf messages can contain fields that are not defined in the current version of the proto schema. While convenient for development and can help with forward compatibility, this poses a security risk in a public environment.</p>
<p>Here&rsquo;s why you should consider dropping unknown fields when exposing gRPC to the internet:</p>
<ul>
<li><strong>Preventing Malicious Data:</strong> Unknown fields can be exploited by malicious actors to inject unexpected data into your service. This could lead to potential security vulnerabilities like code injection or unexpected behavior.</li>
<li><strong>Ensuring Compatibility:</strong> Uncontrolled unknown fields can cause compatibility issues if your clients are using different versions of the proto schema. Dropping them enforces stricter adherence to the defined message format.</li>
<li><strong>Improving Performance:</strong> Skipping unknown fields during message parsing can lead to performance gains, especially when dealing with large datasets.</li>
</ul>
<h3 id="how-to-drop-unknown-fields">How to Drop Unknown Fields</h3>
<p>Here is how you can drop unknown fields while using the standard <code>proto.UnmarshalOptions</code> struct provided by the <code>google.golang.org/protobuf/proto</code> package. Here&rsquo;s how to do it in your Go code:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;google.golang.org/protobuf/proto&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1">...</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Configure unmarshalling options to discard unknown fields</span>
</span></span><span style="display:flex;"><span>opts <span style="color:#81a1c1">:=</span> proto<span style="color:#eceff4">.</span>UnmarshalOptions<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	DiscardUnknown<span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic">// Use the options when unmarshalling incoming messages</span>
</span></span><span style="display:flex;"><span>msg <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>MyMessage<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>err <span style="color:#81a1c1">:=</span> proto<span style="color:#eceff4">.</span><span style="color:#88c0d0">Unmarshal</span><span style="color:#eceff4">(</span>data<span style="color:#eceff4">,</span> msg<span style="color:#eceff4">,</span> opts<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Handle error</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>By setting the <code>DiscardUnknown</code> field to <code>true</code> in the <code>proto.UnmarshalOptions</code> struct before unmarshalling incoming messages, you ensure that any unknown fields are ignored. This helps mitigate the security risks associated with unknown fields while processing internet-facing gRPC requests.</p>
<h2 id="how-to-drop-unknown-fields-in-connect-rpc-servers">How to Drop Unknown Fields in Connect RPC Servers</h2>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;golang.org/x/net/http2&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;golang.org/x/net/http2/h2c&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;go.akshayshah.org/connectproto&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	greeter <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>GreetServer<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>	mux <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServeMux</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	path<span style="color:#eceff4">,</span> handler <span style="color:#81a1c1">:=</span> greetv1connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewGreetServiceHandler</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>		greeter<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Add an option that customizes protobuf marshalling/unmarshalling behavior</span>
</span></span><span style="display:flex;"><span>		connectproto<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithBinary</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>			proto<span style="color:#eceff4">.</span>MarshalOptions<span style="color:#eceff4">{},</span>
</span></span><span style="display:flex;"><span>			proto<span style="color:#eceff4">.</span>UnmarshalOptions<span style="color:#eceff4">{</span>DiscardUnknown<span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>		<span style="color:#616e87;font-style:italic">// Add an option to customize JSON marshalling/unmachalling</span>
</span></span><span style="display:flex;"><span>		connectproto<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithJSON</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>			protojson<span style="color:#eceff4">.</span>MarshalOptions<span style="color:#eceff4">{},</span>
</span></span><span style="display:flex;"><span>			protojson<span style="color:#eceff4">.</span>UnmarshalOptions<span style="color:#eceff4">{</span>DiscardUnknown<span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">Handle</span><span style="color:#eceff4">(</span>path<span style="color:#eceff4">,</span> handler<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatal</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServe</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a3be8c">&#34;localhost:9000&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		h2c<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewHandler</span><span style="color:#eceff4">(</span>mux<span style="color:#eceff4">,</span> <span style="color:#81a1c1">&amp;</span>http2<span style="color:#eceff4">.</span>Server<span style="color:#eceff4">{}),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>In this example, <code>connectproto.WithBinary</code> ensures only messages with defined fields are processed, enhancing the security of your gRPC service. <code>connectproto.WithJSON</code> does the same thing but with JSON.</p>
<h3 id="additional-considerations">Additional Considerations</h3>
<p>While dropping unknown fields is a valuable security practice, it&rsquo;s important to consider potential trade-offs:</p>
<ul>
<li><strong>Backward compatibility:</strong> Clients using older versions of the proto schema will encounter errors if they rely on previously defined unknown fields.</li>
<li><strong>Logging and Debugging:</strong> Dropping unknown fields might make it harder to identify the source of unexpected behavior during development or debugging.</li>
</ul>
<p>In such cases, it&rsquo;s recommended to document these trade-offs and have a clear versioning policy for your gRPC service and client applications.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Exposing gRPC services to the internet requires careful security considerations. By customizing protobuf encoding options, specifically by dropping unknown fields using <code>proto.UnmarshalOptions</code>, you can significantly improve the security posture of your service. Remember to weigh the benefits against potential drawbacks and implement a solution that aligns with your specific needs.</p>
]]></content:encoded></item><item><title>RESTless: Web APIs After REST</title><link>https://kmcd.dev/posts/restless/</link><pubDate>Tue, 26 Mar 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/restless/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/restless/cover_hu_76b7adf3bf2226c0.webp" /> &lt;/p>
                
                Web APIs are the backbone of the modern web, but the ever-evolving landscape demands a rethink. This article explores alternatives to the traditional REST approach, diving into solutions like GraphQL, gRPC, and WebSockets. Unlock the full potential of your APIs and discover a world beyond REST!
                </description><content:encoded><![CDATA[<p>The &ldquo;RESTful API&rdquo; has been the workhorse of the web for many years. It has been an ever-changing religion with tenants that developers try their hardest to adhere to. But as web applications evolve, user demands grow and our industry experience with API design grows, it&rsquo;s time to re-evaluate this approach. This article explores the limitations of REST and delves into modern alternatives that can unlock a world of possibilities beyond.</p>
<h3 id="objects-more-like-objnoxious">Objects? More like &ldquo;objnoxious&rdquo;</h3>
<p>Imagine building a social media API endpoint to retrieve a user&rsquo;s feed. Using a single REST object to represent a feed item can get messy. This object would need to encompass:</p>
<ul>
<li>User information (username, profile picture)</li>
<li>Post content (text, images, videos)</li>
<li>User interactions (likes, comments, shares) with timestamps</li>
<li>Additional data like post visibility or author verification status</li>
</ul>
<p>This &ldquo;feed&rdquo; object becomes bloated, especially if the feed contains many posts. Fetching and updating this complex object for every feed interaction can be inefficient. You can split the object up into many child objects but you are likely creating the need for clients to make more requests and greatly increasing the complexity of the API.</p>
<p>This highlights a limitation of REST: forcing real-world entities (like a social media feed) into rigid object structures with a strict hierarchy can lead to cumbersome data management.</p>
<h3 id="versioning-is-weird">Versioning is weird</h3>
<p>Let&rsquo;s say you introduce a new field to your product data model in a REST API. Versioning in the path (e.g., <code>/api/v2/products</code>) forces you to update every single endpoint URL that uses that data. Versioning each path by adding a query parameter (e.g., <code>/api/products?version=2</code>) or header seems more targeted, but what if only a specific endpoint needs the new version? Do you maintain a list of versions per endpoint? Do you bump the version for the entire API and default to the latest version? This is open to interpretation and every solution seems awkward to me.</p>
<h3 id="limited-options-clever-solution">Limited Options, Clever Solution</h3>
<p>REST only offers a handful of methods (GET, POST, etc.) to handle data. There are <strong>9</strong> methods in total. Let&rsquo;s talk about each one.</p>
<ul>
<li>CONNECT</li>
<li>DELETE</li>
<li>GET</li>
<li>HEAD</li>
<li>OPTIONS</li>
<li>PATCH</li>
<li>POST</li>
<li>PUT</li>
<li>TRACE</li>
</ul>
<p>Most of these are used in niche, hyper-specific ways. I suspect most developers don&rsquo;t know what <code>HEAD</code>, <code>TRACE</code>, <code>OPTIONS</code>, and <code>CONNECT</code> do. None of these are incredibly useful when developing web APIs. So let&rsquo;s ditch them. Let&rsquo;s also ditch <code>PATCH</code> because it&rsquo;s just <code>PUT</code> in a trenchcoat.</p>
<p>Now we have: <code>GET</code>, <code>POST</code>, <code>DELETE</code>, and <code>PUT</code>. Sweet, we&rsquo;re left with enough methods to make a CRUD application. Roughly speaking:</p>
<ul>
<li><strong>C</strong>reate = <code>POST</code></li>
<li><strong>R</strong>ead = <code>GET</code></li>
<li><strong>U</strong>pdate = <code>PUT</code></li>
<li><strong>D</strong>elete = <code>DELETE</code></li>
</ul>
<p>Wow! Such simple. So elegant. I&rsquo;m sure glad that everything web developers do boils down to these 4 simple actions&hellip; Oh wait, that&rsquo;s <strong><em>totally</em> wrong</strong>. There are so many more actions you can do to an object. Just think of how crazy it would be if you were using a programming language where you could only have four pre-defined methods in each class. Think of all of the extra container classes and weird abstractions we&rsquo;d make on top of that. This sounds like utter insanity and is exactly what REST gives us.</p>
<p>Let&rsquo;s stop pretending that there are only 9 (but actually 4) things you can do to a resource.</p>
<h3 id="inefficiency-of-json">Inefficiency of JSON</h3>
<p>JSON is slow and inefficient. It&rsquo;d be insane if we built the internet around this format. Wait, we did? Really?  JSON is wasteful in many ways. It&rsquo;s text-based, which has an inherent cost in payload size and processing. JSON also will include key names over and over again and the length of the keys directly translates to longer payloads. This is not ideal if we&rsquo;re trying to save on data transfer. Protobuf, on the other hand, is a compact and efficient binary format specifically designed for data serialization. Even factoring in gzip, JSON loses out to encoded protobuf <a href="https://auth0.com/blog/beating-json-performance-with-protobuf/" rel="external">in pretty much every way</a>: CPU usage, memory usage, message size and speed. This just shows that there are better formats than JSON.</p>
<h3 id="openapi">OpenAPI?</h3>
<p><a href="https://www.openapis.org/" rel="external">OpenAPI</a> is a specification for describing RESTful APIs. It acts as a contract between API providers and consumers, defining the available resources, their properties, and the allowed operations (GET, POST, PUT, DELETE) for each. A common way OpenAPI is used is by generating OpenAPI specifications from the source of the backend service. Depending on the level of library integration these tools can automatically discover the HTTP method, route, request and response types, etc. This can help keep documentation up-to-date compared to manually creating OpenAPI spec, which is pretty incredible.</p>
<p>Okay, now here&rsquo;s where I get philosophical. If we&rsquo;re going to have a declarative specification for our APIs, I believe the specification should be the source of truth rather than the output. In my mind, OpenAPI specification should be the very first thing you write and agree upon and the servers and clients should be. However, many people don&rsquo;t do this because the tooling isn&rsquo;t amazing. Developers have grown fond of specific libraries and frameworks to develop our APIs so the target for generating language/framework/library code is vast and appears to be an incredibly hard problem. OpenAPI tries to solve so many problems at once. It&rsquo;s coming in after we&rsquo;ve designed our APIs and is trying to describe what&rsquo;s already there. It&rsquo;s an afterthought.</p>
<p>I&rsquo;ve attempted to go down the route of using OpenAPI to generate server stubs and clients. It didn&rsquo;t end well. There were too many issues generating clients and servers, even when the OpenAPI specification was valid. So I had to edit our OpenAPI spec to fit the limitations of the code generators just to get code generation to work. And that was just the beginning of my problems. I contend that this is a natural result of the design goals of the project. OpenAPI was not designed for generating code this way as a priority, so with many complex scenarios, it can be unclear how to map the spec to the semantics of the language/framework/library. OpenAPI wasn&rsquo;t designed for that, it was only designed to describe the API, not the server or client that produces or consumes it. Now consider just how many &ldquo;targets&rdquo; for client and server stubs you want. Now consider that target libraries and the OpenAPI spec itself are evolving. This results in a compatibility matrix from hell.</p>
<h2 id="so-what-other-options-do-we-have">So what other options do we have?</h2>
<p>REST <em>has</em> served us well, but the modern web demands more. Let&rsquo;s explore the exciting alternatives that offer a range of benefits and functionalities:</p>
<h3 id="graphql">GraphQL</h3>
<p>The &ldquo;Choose Your Own Adventure&rdquo; API. Still with the social media app example, imagine fetching only the user&rsquo;s name and profile picture for the feed view, and then requesting their full profile and friend list separately when a user clicks on their profile. GraphQL allows you to specify exactly the data you need for each view, reducing unnecessary data transfer.</p>
<p>This method also has its downsides like making the backend API extremely complex.</p>
<h3 id="grpc">gRPC</h3>
<p>The &ldquo;Cut the Drama&rdquo; API. Consider a mobile game that communicates with a game server. gRPC allows you to define remote procedures (like <code>attackEnemy</code> or <code>usePowerUp</code>) that the client can call directly on the server. This removes the need for complex REST resource mapping and makes the communication intent clear. There are variants of gRPC like <a href="https://kmcd.dev/posts/connectrpc/">ConnectRPC</a> that allow for leveraging of HTTP GET requests so you can fully leverage browser caching.</p>
<h3 id="websockets">WebSockets</h3>
<p>Need real-time updates like a live chat or stock ticker? WebSockets offer a persistent two-way communication channel, ideal for constantly flowing data between client and server. This is different from REST&rsquo;s request-response cycle, allowing for a more dynamic connection.</p>
<h3 id="server-sent-events-sse">Server-Sent Events (SSE)</h3>
<p>SSE allows the server to push updates to the client without the client needing to constantly ask. Imagine live sports scores or social media notifications. SSE is simpler to implement than WebSockets, but is one-way (server to client).</p>
<p>Here&rsquo;s a conclusion that summarizes the key points and offers a final thought:</p>
<p><strong>The Verdict: REST vs. the Rest</strong></p>
<p>REST APIs have served us faithfully for years, but as our applications become more complex and data-hungry, it&rsquo;s worth considering the alternatives. GraphQL offers flexibility in data fetching, gRPC provides clear and efficient communication, WebSockets enable bidirectional real-time data flow and great browser support, and SSE simplifies server-to-client updates.</p>
<p>The choice ultimately depends on your specific needs. But remember, the API landscape is ever-evolving. HTTP/2 and HTTP/3 have opened up some new functionality that we have yet to fully tap into with our API designs. So, keep an open mind, explore the options, and don&rsquo;t be afraid to break free from the comfy (but maybe slightly threadbare) jeans of REST when a more fitting alternative emerges.</p>
]]></content:encoded></item><item><title>Introducing unknownconnect-go</title><link>https://kmcd.dev/posts/unknownconnect-go/</link><pubDate>Tue, 19 Mar 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/unknownconnect-go/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/unknownconnect-go/cover_hu_4bccb0bea96d4f9c.webp" /> &lt;/p>
                
                unknownconnect-go is library that helps developers using gRPC identify compatibility issues caused by mismatched message definitions.
                </description><content:encoded><![CDATA[<p>gRPC systems can be quite complex. When making additions to protobuf files the server or the client often gets updated at different times. In a perfect world, this would all be synchronized. But we live in reality. Sometimes release schedules differ between components. Sometimes you just forget to update a component. Many times you might be consuming a gRPC service managed by another team and <em>they don&rsquo;t tell you that they&rsquo;re changing things</em>. I made something that will bring unique insight into this problem with very little work.</p>
<h2 id="lets-make-things-better">Let&rsquo;s make things better</h2>
<p><a href="https://github.com/sudorandom/unknownconnect-go/" rel="external"><code>unknownconnect-go</code></a> is an interceptor for <a href="https://connectrpc.com/" rel="external">ConnectRPC</a> clients and servers that tells you if you are receiving protobuf messages with unknown fields. Now you can know when you should upgrade your gRPC clients or servers to the latest version. Let&rsquo;s discuss how to use it.</p>
<ol>
<li><strong>Install the library:</strong></li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>go get -u github.com/sudorandom/unknownconnect-go
</span></span></code></pre></div><ol start="2">
<li><strong>Import the library:</strong></li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>    unknownconnect <span style="color:#a3be8c">&#34;github.com/sudorandom/unknownconnect-go&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span></code></pre></div><h2 id="server-side-usage">Server-side usage</h2>
<p>Here are two examples demonstrating how to use <code>unknownconnect-go</code> on the server side:</p>
<p><strong>Short example:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>unknownconnect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewInterceptor</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>    unknownconnect<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithCallback</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>ctx context<span style="color:#eceff4">.</span>Context<span style="color:#eceff4">,</span> spec connect<span style="color:#eceff4">.</span>Spec<span style="color:#eceff4">,</span> msg proto<span style="color:#eceff4">.</span>Message<span style="color:#eceff4">)</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Warn</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;received a protobuf message with unknown fields&#34;</span><span style="color:#eceff4">,</span> slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Any</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;spec&#34;</span><span style="color:#eceff4">,</span> spec<span style="color:#eceff4">),</span> slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Any</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;msg&#34;</span><span style="color:#eceff4">,</span> msg<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}),</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span></code></pre></div><p>This example creates a new interceptor using <code>unknownconnect.NewInterceptor</code>. The interceptor function receives an <code>unknownconnect.WithCallback</code> argument that provides a callback function. This function takes three arguments:</p>
<ul>
<li><code>ctx</code>: The <a href="https://pkg.go.dev/context#Context" rel="external">context</a> object</li>
<li><code>spec</code>: The <a href="https://pkg.go.dev/connectrpc.com/connect#Spec" rel="external">gRPC service specification</a></li>
<li><code>msg</code>: The received <a href="https://pkg.go.dev/google.golang.org/protobuf/proto#Message" rel="external">protobuf message</a>. Note that the actual message with unknown field(s) can be nested deeper within this message.</li>
</ul>
<p>In the previous example, when a message with unknown fields is received, the interceptor will log a warning message using the <code>slog.Warn</code> function. It includes information about the message specification and the message itself.</p>
<p><strong>Full example:</strong>
Here is a full example that shows you how to register the <code>unknownconnect.Interceptor</code> with a ConnectRPC handler:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    greeter <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>GreetServer<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>    mux <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServeMux</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>    path<span style="color:#eceff4">,</span> handler <span style="color:#81a1c1">:=</span> greetv1connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewGreetServiceHandler</span><span style="color:#eceff4">(</span>greeter<span style="color:#eceff4">,</span> connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithInterceptors</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>        unknownconnect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewInterceptor</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>            unknownconnect<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithCallback</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>ctx context<span style="color:#eceff4">.</span>Context<span style="color:#eceff4">,</span> spec connect<span style="color:#eceff4">.</span>Spec<span style="color:#eceff4">,</span> msg proto<span style="color:#eceff4">.</span>Message<span style="color:#eceff4">)</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>                <span style="color:#81a1c1;font-weight:bold">return</span> connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewError</span><span style="color:#eceff4">(</span>connect<span style="color:#eceff4">.</span>InvalidArgument<span style="color:#eceff4">,</span> errors<span style="color:#eceff4">.</span><span style="color:#88c0d0">New</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;protobuf version missmatch; received unknown fields&#34;</span><span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>            <span style="color:#eceff4">}),</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>    mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">Handle</span><span style="color:#eceff4">(</span>path<span style="color:#eceff4">,</span> handler<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    http<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServe</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;localhost:8080&#34;</span><span style="color:#eceff4">,</span> h2c<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewHandler</span><span style="color:#eceff4">(</span>mux<span style="color:#eceff4">,</span> <span style="color:#81a1c1">&amp;</span>http2<span style="color:#eceff4">.</span>Server<span style="color:#eceff4">{}))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>The interceptor function in this example returns an error with the <code>connect.InvalidArgument</code> code, which will cause the server to reject the request if it receives a message with unknown fields.</p>
<p><strong>Customization options:</strong></p>
<p>The two examples above show two ways to handle messages with unknown fields but you can customize the behavior of the interceptor to suit your specific needs. Here are some ideas:</p>
<ul>
<li><strong>Log the event:</strong> As shown in the first example, you can simply log a warning message when an unknown field is encountered. This can help debug and monitor the cause.</li>
<li><strong>Add to a metric</strong> With this approach, you can emit metrics whenever unknown fields are encountered. This can be helpful to give more monitoring insight.</li>
<li><strong>Fail the request/response:</strong> This approach, demonstrated in the second example, can be useful in pre-production environments to prevent unexpected behavior caused by mismatched message definitions.</li>
<li><strong>Add an annotation to the context:</strong> This allows you to pass information about the unknown field to your service handler.</li>
</ul>
<h2 id="client-side-usage">Client-side usage</h2>
<p>And it works the same for clients, too:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;context&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;log/slog&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    greetv1 <span style="color:#a3be8c">&#34;example/gen/greet/v1&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;example/gen/greet/v1/greetv1connect&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;connectrpc.com/connect&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    client <span style="color:#81a1c1">:=</span> greetv1connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewGreetServiceClient</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>        http<span style="color:#eceff4">.</span>DefaultClient<span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a3be8c">&#34;http://localhost:8080&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>        connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithInterceptors</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>            unknownconnect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewInterceptor</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>                unknownconnect<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithCallback</span><span style="color:#eceff4">(</span><span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>ctx context<span style="color:#eceff4">.</span>Context<span style="color:#eceff4">,</span> spec connect<span style="color:#eceff4">.</span>Spec<span style="color:#eceff4">,</span> msg proto<span style="color:#eceff4">.</span>Message<span style="color:#eceff4">)</span> <span style="color:#81a1c1">error</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>                    slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Warn</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;received a protobuf message with unknown fields&#34;</span><span style="color:#eceff4">,</span> slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Any</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;spec&#34;</span><span style="color:#eceff4">,</span> spec<span style="color:#eceff4">),</span> slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Any</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;msg&#34;</span><span style="color:#eceff4">,</span> msg<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#81a1c1;font-weight:bold">return</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span>                <span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>            <span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    res<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> client<span style="color:#eceff4">.</span><span style="color:#88c0d0">Greet</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>        context<span style="color:#eceff4">.</span><span style="color:#88c0d0">Background</span><span style="color:#eceff4">(),</span>
</span></span><span style="display:flex;"><span>        connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewRequest</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>greetv1<span style="color:#eceff4">.</span>GreetRequest<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Jane&#34;</span><span style="color:#eceff4">}),</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Error</span><span style="color:#eceff4">(</span>err<span style="color:#eceff4">.</span><span style="color:#88c0d0">Error</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>        <span style="color:#81a1c1;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>    slog<span style="color:#eceff4">.</span><span style="color:#88c0d0">Info</span><span style="color:#eceff4">(</span>res<span style="color:#eceff4">.</span>Msg<span style="color:#eceff4">.</span>Greeting<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>This example works in a similar way to how the server interceptor. It creates a new gRPC client for the Greet service and adds the unknownconnect interceptor using the <code>connect.WithInterceptors</code> function.</p>
<h2 id="conclusion">Conclusion</h2>
<p><a href="https://github.com/sudorandom/unknownconnect-go/" rel="external"><code>unknownconnect-go</code></a> provides a simple and effective way to identify potential compatibility issues in your gRPC systems by detecting messages with unknown fields. It offers flexibility in how you handle these situations, allowing you to log warnings, reject requests, or implement custom logic as needed. By integrating <code>unknownconnect-go</code> into your development workflow, you can gain valuable insights into potential version mismatches and ensure smoother operation of your gRPC systems.</p>
<ul>
<li><strong>GitHub Link:</strong> <a href="https://github.com/sudorandom/unknownconnect-go/" rel="external">github.com/sudorandom/unknownconnect-go</a></li>
<li><strong>Package documentation:</strong> <a href="https://pkg.go.dev/github.com/sudorandom/unknownconnect-go" rel="external">pkg.go.dev/github.com/sudorandom/unknownconnect-go</a></li>
</ul>
]]></content:encoded></item><item><title>Making gRPC more approachable with ConnectRPC</title><link>https://kmcd.dev/posts/connectrpc/</link><pubDate>Tue, 05 Mar 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/connectrpc/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/connectrpc/cover_hu_b94bf17e0642a707.webp" /> &lt;/p>
                
                Unleash the power of gRPC: ConnectRPC breaks down barriers, enabling frictionless communication between gRPC, gRPC-Web, and any HTTP client.
                </description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>gRPC is an open-source framework for building high-performance applications that communicate with each other. gRPC is language-neutral, meaning clients and servers can be written in different programming languages, and it offers features like authentication, streaming, and load balancing. Also, because it is built using protocolbuffers it gives an amazing way to define contracts in a much more clear way than bolting on swagger to your API after the fact. However, gRPC has some limitations that restrict its usage. It requires special gRPC clients and support for HTTP/2 (which still lacking in some areas), and it doesn&rsquo;t work from Javascript.</p>
<p>Support for Javascript has been solved in a couple of ways. Let&rsquo;s discuss the two approaches!</p>
<h2 id="grpc-web">gRPC-Web</h2>
<p>gRPC-Web is a <a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md" rel="external">variant of gRPC</a> and <a href="https://github.com/grpc/grpc-web" rel="external">gRPC client for Javascript</a> that avoids HTTP/2-specific features like <a href="https://carlmastrangelo.com/blog/why-does-grpc-insist-on-trailers" rel="external">HTTP trailers</a>. This is an incredibly practical solution to the problem. And it does work really well, but I still have my reservations on some of the implementation details.</p>
<p>gRPC-Web doesn&rsquo;t fix the &ldquo;this doesn&rsquo;t look like any HTTP API I&rsquo;ve ever seen&rdquo; issue that I have with gRPC in general. In other words, I want to be able to send a normal cURL example to someone. gRPC-Web doesn&rsquo;t work for that without special gRPC-specific clients or tooling.</p>
<p>Additionally, I don&rsquo;t like how gRPC-Web is typically deployed. You usually are forced into a proxy that can convert gRPC into gRPC-Web. Instead, I prefer the gRPC-Web implementation to sit alongside the actual gRPC server. The protocol isn&rsquo;t so different from the normal gRPC version so it shouldn&rsquo;t be too much work to add support to existing gRPC server implementations. I know that the popular gRPC library <a href="https://docs.rs/tonic/latest/tonic/" rel="external">tonic</a> supports <a href="https://docs.rs/tonic-web/latest/tonic_web/" rel="external">gRPC-Web</a> out of the box.</p>
<h2 id="grpc-transcoding">gRPC Transcoding</h2>
<p>The idea with transcoding is to annotate your protobuf service methods with HTTP verbs and path patterns that can map a more REST-like API to gRPC. Many solutions allow you to provide a separate config file so you aren&rsquo;t required to have the HTTP annotations making a mess of your protobuf files. <a href="https://cloud.google.com/endpoints/docs/grpc/transcoding" rel="external">Google has a service</a> that can use this mapping and provide a REST-like API on top of your gRPC service. and several gRPC proxies can do this kind of transcoding as well like <a href="https://github.com/grpc-ecosystem/grpc-gateway" rel="external">gRPC-Gateway</a> and <a href="https://www.envoyproxy.io" rel="external">envoy</a>.</p>
<p>Here&rsquo;s a simple version of what the annotations can look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">status</span><span style="color:#81a1c1">.</span>v1<span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#a3be8c">&#34;google/protobuf/empty.proto&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#a3be8c">&#34;google/api/annotations.proto&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#a3be8c">&#34;google/rpc/status.proto&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">service</span> Status <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">rpc</span> GetStatus<span style="color:#eceff4">(</span>google.protobuf.Empty<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span> <span style="color:#eceff4">(</span>google.rpc.Status<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">option</span><span style="color:#eceff4">(</span>google.api.http<span style="color:#eceff4">)</span> <span style="color:#81a1c1">=</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>        get<span style="color:#81a1c1">:</span> <span style="color:#a3be8c">&#34;/v1/status&#34;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#eceff4">};</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>You can see how <code>status.v1.Status.GetStatus</code> maps to <code>GET /v1/status</code>. Thanks, protobuf options!</p>
<p>However, most ways of deploying this in Go weirdly use proxies, creating a new network hop and a decoding/encoding step. Additionally, transcoding ruins a lot of the benefits you have from a contract-based interface that gRPC provides. Generated clients generally can&rsquo;t interpret the <code>google.api.http</code> options so if you want to keep to a contract-based model you have to rely on converting your protobuf file to OpenAPI and generating clients based on that. I don&rsquo;t generally prefer this method because it adds extra complexity. However, it can be a really good way to support existing APIs by &ldquo;swapping out&rdquo; traditional HTTP handles with gRPC.</p>
<h2 id="connectrpc">ConnectRPC</h2>
<p>Let me introduce <a href="https://connectrpc.com/" rel="external">ConnectRPC</a>. I believe it elegantly solves all of the issues I have with the gRPC ecosystem. ConnectRPC is a series of libraries for building browser and gRPC-compatible APIs. With ConnectRPC as the server, you get support for three protocols: gRPC, gRPC-Web and <a href="https://connectrpc.com/docs/protocol/" rel="external">the so-called &ldquo;Connect&rdquo; protocol</a>. These three protocols are <a href="https://connectrpc.com/docs/multi-protocol/" rel="external">all served from a single ConnectRPC server</a> by simply using the HTTP content-type header, which gRPC and gRPC-Web clients already send. Let me break down where you might use each protocol:</p>
<ul>
<li>Microservice communication: Connect or gRPC</li>
<li>Publically exposed API: Connect, gRPC or gRPC-Web depending on client language support</li>
<li>Clients running in environments without HTTP/2 support: Connect or gRPC-Web</li>
<li>Sending simple API examples to colleagues: Connect</li>
</ul>
<p>Hopefully, I convinced you to at least give ConnectRPC a try. Now, get started with it! Here are the <a href="https://connectrpc.com/docs/go/getting-started" rel="external">getting started docs for Go</a>. It does a great job at detailing how you make a Go server and client implemented with ConnectRPC.</p>
<h2 id="multiple-protocol-support">Multiple Protocol Support</h2>
<p>So, ConnectRPC is running <em>three</em> different protocols? Isn&rsquo;t that overkill? You can make everything work with the Connect protocol, but there are benefits to supporting all three.</p>
<p>We can use the gRPC protocol to call into this ConnectRPC service using normal gRPC tooling like <a href="https://github.com/fullstorydev/grpcurl" rel="external">grpcurl</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ grpcurl -plaintext <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>          -proto greet/v1/greet.proto <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>          -d <span style="color:#a3be8c">&#39;{&#34;name&#34;: &#34;Jane&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>          127.0.0.1:8080 <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>          greet.v1.GreetService.Greet
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;greeting&#34;</span>: <span style="color:#a3be8c">&#34;Hello, Jane!&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>If you have the <a href="https://github.com/connectrpc/grpcreflect-go" rel="external">reflection API enabled</a>, you can omit the -proto option.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ grpcurl -plaintext <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>          -d <span style="color:#a3be8c">&#39;{&#34;name&#34;: &#34;Jane&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>          127.0.0.1:8080 <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>          greet.v1.GreetService.Greet
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;greeting&#34;</span>: <span style="color:#a3be8c">&#34;Hello, Jane!&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>Now here&rsquo;s where the magic is. In addition to gRPC-specific tooling, I can also use generic HTTP tools with ConnectRPC servers, like <a href="https://curl.se/" rel="external">curl</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl -XPOST <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>       -H<span style="color:#a3be8c">&#34;Content-Type: application/json&#34;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>       -d <span style="color:#a3be8c">&#39;{&#34;name&#34;: &#34;Jane&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>       <span style="color:#a3be8c">&#34;http://127.0.0.1:8080/greet.v1.GreetService/Greet&#34;</span>
</span></span></code></pre></div><p>This shows how the &ldquo;I want to be able to send a normal cURL example to someone&rdquo; desire from above is completely fulfilled.</p>
<p>I also want to point out that there is also the <code>buf curl</code> command which is a CLI tool that allows you to call ConnectRPC services using all three protocols (gRPC, gRPC-Web, Connect).</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl --http2-prior-knowledge <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           -d <span style="color:#a3be8c">&#39;{&#34;name&#34;: &#34;Jane&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           http://127.0.0.1:8080/greet.v1.GreetService/Greet
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;greeting&#34;</span>: <span style="color:#a3be8c">&#34;Hello, Jane!&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>This last command actually uses the Connect protocol. We can pass in <code>--protocol</code> to use the gRPC-Web or the gRPC protocol instead:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ buf curl --http2-prior-knowledge <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           -d <span style="color:#a3be8c">&#39;{&#34;name&#34;: &#34;Jane&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           http://127.0.0.1:8080/greet.v1.GreetService/Greet <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           --protocol<span style="color:#81a1c1">=</span>grpcweb
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;greeting&#34;</span>: <span style="color:#a3be8c">&#34;Hello, Jane!&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>$ buf curl --http2-prior-knowledge <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           -d <span style="color:#a3be8c">&#39;{&#34;name&#34;: &#34;Jane&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           http://127.0.0.1:8080/greet.v1.GreetService/Greet <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           --protocol<span style="color:#81a1c1">=</span>grpc
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;greeting&#34;</span>: <span style="color:#a3be8c">&#34;Hello, Jane!&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><h3 id="digging-deeper-optional">Digging Deeper (optional)</h3>
<p>This is an optional section where we will dig deeper into what is happening here with this last command to show what is happening under the hood. We will see how server reflection works with gRPC and a couple uses of it. We can see more details of the previous <code>buf curl</code> command by adding <code>-v</code> at the end:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl --http2-prior-knowledge <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           -d <span style="color:#a3be8c">&#39;{&#34;name&#34;: &#34;Jane&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           http://127.0.0.1:8080/greet.v1.GreetService/Greet -v
</span></span><span style="display:flex;"><span>buf: * Using server reflection to resolve <span style="color:#a3be8c">&#34;greet.v1.GreetService&#34;</span>
</span></span><span style="display:flex;"><span>buf: * Dialing <span style="color:#81a1c1">(</span>tcp<span style="color:#81a1c1">)</span> 127.0.0.1:8080...
</span></span><span style="display:flex;"><span>buf: * Connected to 127.0.0.1:8080
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) POST /grpc.reflection.v1.ServerReflection/ServerReflectionInfo</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Accept-Encoding: identity</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Content-Type: application/grpc+proto</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Grpc-Accept-Encoding: gzip</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Grpc-Timeout: 119999m</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Te: trailers</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) User-Agent: grpc-go-connect/1.14.0 (go1.21.6) buf/1.29.0</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1)</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">}</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) [5 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">}</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) [23 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) HTTP/2.0 200 OK</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Content-Type: application/grpc+proto</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Date: Sat, 02 Mar 2024 06:41:48 GMT</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Grpc-Accept-Encoding: gzip</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Grpc-Encoding: gzip</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1)</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">{</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) [5 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">{</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) [244 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: * Server reflection has resolved file <span style="color:#a3be8c">&#34;greet/v1/greet.proto&#34;</span>
</span></span><span style="display:flex;"><span>buf: * Invoking RPC greet.v1.GreetService.Greet
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) POST /greet.v1.GreetService/Greet</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Accept-Encoding: identity</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Content-Type: application/grpc+proto</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Grpc-Accept-Encoding: gzip</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Grpc-Timeout: 119994m</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Te: trailers</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) User-Agent: grpc-go-connect/1.14.0 (go1.21.6) buf/1.29.0</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2)</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">}</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) [5 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">}</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) [6 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: * <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Finished upload</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) HTTP/2.0 200 OK</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Content-Type: application/grpc+proto</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Date: Sat, 02 Mar 2024 06:41:48 GMT</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Greet-Version: v1</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Grpc-Accept-Encoding: gzip</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Grpc-Encoding: gzip</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2)</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">{</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) [5 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">{</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) [38 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2)</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Grpc-Message:</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Grpc-Status: 0</span>
</span></span><span style="display:flex;"><span>buf: * <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Call complete</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;greeting&#34;</span>: <span style="color:#a3be8c">&#34;Hello, Jane!&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1)</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Grpc-Message:</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Grpc-Status: 0</span>
</span></span><span style="display:flex;"><span>buf: * <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Call complete</span>
</span></span></code></pre></div><p>This is a little overwhelming at first so I will break down the two different calls that are being made here. Because we aren&rsquo;t passing the protobuf file (or <a href="https://protobuf.com/docs/descriptors" rel="external">descriptors</a>) as the <code>--schema</code> option we are missing some information needed to generate the protobuf messages from the given JSON string. So that&rsquo;s the first call that <code>buf curl</code> will make, using the <a href="https://github.com/grpc/grpc/blob/master/doc/server-reflection.md" rel="external">Server Reflection</a> API:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) POST /grpc.reflection.v1.ServerReflection/ServerReflectionInfo</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Accept-Encoding: identity</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Connect-Accept-Encoding: gzip</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Connect-Protocol-Version: 1</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Connect-Timeout-Ms: 119999</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Content-Type: application/connect+proto</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) User-Agent: connect-go/1.14.0 (go1.21.6) buf/1.29.0</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1)</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">}</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) [5 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">}</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) [23 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) HTTP/2.0 200 OK</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Connect-Accept-Encoding: gzip</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Connect-Content-Encoding: gzip</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Content-Type: application/connect+proto</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Date: Sat, 02 Mar 2024 06:33:57 GMT</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1)</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">{</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) [5 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">{</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) [244 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: * Server reflection has resolved file <span style="color:#a3be8c">&#34;greet/v1/greet.proto&#34;</span>
</span></span></code></pre></div><p>It acquired a version of the <code>greet.proto</code> file from the server. Now our client has everything it needs to make the actual request where it calls the actual service.</p>
<p>With HTTP, that service lives at <code>POST /greet.v1.GreetService/Greet</code>. Here&rsquo;s what the call looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>buf: * Invoking RPC greet.v1.GreetService.Greet
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) POST /greet.v1.GreetService/Greet</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Accept-Encoding: identity</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Content-Type: application/grpc+proto</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Grpc-Accept-Encoding: gzip</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Grpc-Timeout: 119994m</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Te: trailers</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) User-Agent: grpc-go-connect/1.14.0 (go1.21.6) buf/1.29.0</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2)</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">}</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) [5 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">}</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) [6 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: * <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Finished upload</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) HTTP/2.0 200 OK</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Content-Type: application/grpc+proto</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Date: Sat, 02 Mar 2024 06:41:48 GMT</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Greet-Version: v1</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Grpc-Accept-Encoding: gzip</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Grpc-Encoding: gzip</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2)</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">{</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) [5 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">{</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) [38 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2)</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Grpc-Message:</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Grpc-Status: 0</span>
</span></span><span style="display:flex;"><span>buf: * <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#2) Call complete</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;greeting&#34;</span>: <span style="color:#a3be8c">&#34;Hello, Jane!&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1)</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Grpc-Message:</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Grpc-Status: 0</span>
</span></span><span style="display:flex;"><span>buf: * <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Call complete</span>
</span></span></code></pre></div><p>I wanted to show you how the server reflection works because it is a really good strength of gRPC. You can expose an API and have it be completely discoverable. Tools can automatically use this discovery mechanism&hellip; but so can humans. Look at these commands with <code>grpcurl</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ grpcurl -plaintext 127.0.0.1:8080 list
</span></span><span style="display:flex;"><span>greet.v1.GreetService
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$ grpcurl -plaintext 127.0.0.1:8080 describe
</span></span><span style="display:flex;"><span>greet.v1.GreetService is a service:
</span></span><span style="display:flex;"><span>service GreetService <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  rpc Greet <span style="color:#81a1c1">(</span> .greet.v1.GreetRequest <span style="color:#81a1c1">)</span> returns <span style="color:#81a1c1">(</span> .greet.v1.GreetResponse <span style="color:#81a1c1">)</span><span style="color:#eceff4">;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$ grpcurl -plaintext 127.0.0.1:8080 describe .greet.v1.GreetRequest
</span></span><span style="display:flex;"><span>greet.v1.GreetRequest is a message:
</span></span><span style="display:flex;"><span>message GreetRequest <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  string name <span style="color:#81a1c1">=</span> 1<span style="color:#eceff4">;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>We can see all the available services using the <code>list</code> and <code>describe</code> commands. And if you pass an object to the <code>describe</code> command you can dig down into message definitions. Protobuf works well here as the API contract for our services.</p>
<h2 id="conclusion">Conclusion</h2>
<p>ConnectRPC offers a compelling solution for building gRPC servers that support multiple protocols. It seamlessly integrates gRPC, gRPC-Web, and the Connect protocol, allowing clients written in various languages and environments to interact with your service.</p>
<p>Here are the key takeaways:</p>
<ul>
<li>ConnectRPC eliminates the limitations of traditional gRPC by supporting HTTP/1.1 and Javascript environments.</li>
<li>It provides a familiar REST-like API through the Connect protocol while still leveraging the benefits of Protobuf for contracts.</li>
<li>Multiple protocol support with a single server simplifies deployment and reduces complexity.</li>
<li>Existing gRPC tooling can still be used for server reflection and making gRPC requests.</li>
<li>The Connect protocol itself can be used with generic tools like curl, making API exploration a breeze.</li>
</ul>
<p>If you&rsquo;re looking for a flexible and future-proof way to build gRPC APIs, ConnectRPC is definitely worth considering.  I highly recommend checking out the <a href="https://connectrpc.com/docs/go/getting-started" rel="external">getting started guide for Go</a> for a hands-on approach to using ConnectRPC.</p>
]]></content:encoded></item><item><title>Inspecting Protobuf Messages</title><link>https://kmcd.dev/posts/inspecting-protobuf-messages/</link><pubDate>Sun, 25 Feb 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/inspecting-protobuf-messages/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/inspecting-protobuf-messages/cover_hu_9163ecae201b3fce.webp" /> &lt;/p>
                
                
                </description><content:encoded><![CDATA[<p><a href="https://protobuf.dev/" rel="external">Protocol Buffers</a> is an amazing message format. It&rsquo;s <a href="https://nilsmagnus.github.io/post/proto-json-sizes/" rel="external">incredibly compact</a> and <a href="https://medium.com/@akresling/go-benchmark-json-v-protobuf-4ec3c62ec8d4" rel="external">performant</a>. However, these advantages come at a cost. Since Protobuf is a binary format it lacks a lot in readability compared to text-based formats like JSON or XML. If you look at encoded protobuf data it just looks like meaningless ones and zeros.</p>
<p>However, all hope is not lost. Even if you just have a binary protobuf file with no knowledge of the corresponding protobuf file we can still get some information out of it. Let me introduce a tool called <a href="https://github.com/protocolbuffers/protoscope" rel="external">Protoscope</a>. Protoscope is a tool for inspecting protobuf binary. It can do this with or without the protobuf files or the equivalent <a href="https://protobuf.com/docs/descriptors" rel="external">descriptor set</a> (but it can do a better job with the protobuf data).</p>
<hr>
<h3 id="install-protoscope">Install Protoscope</h3>
<p>Okay, let&rsquo;s hit the ground running. Here&rsquo;s how to install protoscope (<a href="https://go.dev/dl/" rel="external">requires go</a>).</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>go install github.com/protocolbuffers/protoscope/cmd/protoscope@latest
</span></span></code></pre></div><h3 id="using-protoscope">Using Protoscope</h3>
<p>If you have a binary protobuf file, here&rsquo;s what you can run to get protoscope output:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># protoscope [filename]</span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># variety.pb contains binary protobuf content.</span>
</span></span><span style="display:flex;"><span>$ protoscope -explicit-wire-types variety.pb
</span></span><span style="display:flex;"><span>1:LEN <span style="color:#81a1c1">{</span><span style="color:#a3be8c">&#34;Hello World!&#34;</span><span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>2:VARINT <span style="color:#b48ead">3</span>
</span></span><span style="display:flex;"><span>3:VARINT <span style="color:#b48ead">175</span>
</span></span><span style="display:flex;"><span>4:LEN <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">`</span>736563726574fa43bcab0ddfd7f3582699331e7ebbe267196804216a885fad5a3b0b01da25577220<span style="color:#a3be8c">`</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">`</span>f583a5ac18b5c28516de341db3a7b44226e21ed85a6cdb571019fbee016574b8b99cd4ceab728ddd<span style="color:#a3be8c">`</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">`</span>34a3e0b54605f7c7d1181ee3e13f4d9a07655f6ec843e74a997fd4b8ab87dc61754a60bd513d0121<span style="color:#a3be8c">`</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">`</span>e4ad1fdc9e07a632<span style="color:#a3be8c">`</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>5:LEN <span style="color:#81a1c1">{</span><span style="color:#a3be8c">`</span>01020304<span style="color:#a3be8c">`</span><span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>6:LEN <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  1:LEN <span style="color:#81a1c1">{</span><span style="color:#a3be8c">&#34;Fluffy&#34;</span><span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>  2:VARINT <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>7:VARINT <span style="color:#b48ead">921</span>
</span></span><span style="display:flex;"><span>8:VARINT <span style="color:#b48ead">1</span>
</span></span><span style="display:flex;"><span>9:I32 1.2345i32   <span style="color:#616e87;font-style:italic"># 0x3f9e0419i32</span>
</span></span><span style="display:flex;"><span>10:LEN <span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  1:LEN <span style="color:#81a1c1">{</span><span style="color:#a3be8c">&#34;key&#34;</span><span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>  2:LEN <span style="color:#81a1c1">{</span>1:LEN <span style="color:#81a1c1">{</span><span style="color:#a3be8c">&#34;Fred&#34;</span><span style="color:#81a1c1">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>19:LEN <span style="color:#81a1c1">{</span><span style="color:#a3be8c">`</span>ffffffffffffffffff01feffffffffffffffff01fdffffffffffffffff01fcffffffffffffffff01<span style="color:#a3be8c">`</span><span style="color:#81a1c1">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># Often times you might see binary files encoded with hexadecimal.</span>
</span></span><span style="display:flex;"><span>$ cat variety.pb.hex
</span></span><span style="display:flex;"><span>0a0c48656c6c6f20576f726c6421100318af01228001736563726574fa43bcab0ddfd7f3582699331e7ebbe267196804216a885fad5a3b0b01da25577220f583a5ac18b5c28516de341db3a7b44226e21ed85a6cdb571019fbee016574b8b99cd4ceab728ddd34a3e0b54605f7c7d1181ee3e13f4d9a07655f6ec843e74a997fd4b8ab87dc61754a60bd513d0121e4ad1fdc9e07a6322a0401020304320a0a06466c75666679100138990740014d19049e3f520d0a036b657912060a04467265649a0128ffffffffffffffffff01feffffffffffffffff01fdffffffffffffffff01fcffffffffffffffff01
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"># We can use xxd to convert it to binary then pipe it into protoscope</span>
</span></span><span style="display:flex;"><span>$ xxd -r -ps variety.pb.hex <span style="color:#eceff4">|</span> protoscope -explicit-wire-types
</span></span><span style="display:flex;"><span>... <span style="color:#81a1c1">[</span>same as above<span style="color:#81a1c1">]</span> ...
</span></span></code></pre></div><hr>
<p>Note that with the examples above protoscope needs to guess types because we did not pass in the <code>-descriptor-set</code> and <code>-message-type</code> options. Why does protoscope need to guess types? One thing that will help with understanding this topic is knowing that <em>protobuf encoding only has <em>6 types</em>.</em> And two of them aren&rsquo;t even used in the latest version. You may be saying to yourself &ldquo;I remember seeing a <a href="https://protobuf.dev/programming-guides/proto3/#scalar" rel="external">table of protobuf types</a> and it had way more than 6!&rdquo; and you would be correct. Although protobufs support many types they are all serialized into 6 &ldquo;wire types.&rdquo;:</p>
<table>
  <thead>
      <tr>
          <th>ID</th>
          <th>Name</th>
          <th>Used for</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0</td>
          <td>VARINT</td>
          <td>int32, int64, uint32, uint64, sint32, sint64, bool, enum</td>
      </tr>
      <tr>
          <td>1</td>
          <td>I64</td>
          <td>fixed64, sfixed64, double</td>
      </tr>
      <tr>
          <td>2</td>
          <td>LEN</td>
          <td>string, bytes, embedded messages, packed repeated fields</td>
      </tr>
      <tr>
          <td>3</td>
          <td>SGROUP</td>
          <td>group start (deprecated)</td>
      </tr>
      <tr>
          <td>4</td>
          <td>EGROUP</td>
          <td>group end (deprecated)</td>
      </tr>
      <tr>
          <td>5</td>
          <td>I32</td>
          <td>fixed32, sfixed32, float</td>
      </tr>
  </tbody>
</table>
<p>How protobuf encodes each protobuf type into a wire type differs depending on the type. The full explanation exists in <a href="https://protobuf.dev/programming-guides/encoding/" rel="external">the programming guide for the protobuf encoding</a>. I recommend reading through it to fully understand the implications of using the protoscope tool.</p>
<h3 id="strings">Strings</h3>
<p>Let&rsquo;s see what it looks like with a trivial example that is sourced from <a href="https://buf.build/connectrpc/eliza/docs/main:connectrpc.eliza.v1#connectrpc.eliza.v1.SayRequest" rel="external">connectrpc.eliza.v1.SayRequest</a> <code>(protoscope -explicit-wire-types eliza.SayRequest.pb)</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>1:LEN {&#34;Hello World!&#34;}
</span></span></code></pre></div><p>What does this tell us? Well, it tells us the message has a single field with field number 1. It also tells us the value of the string is <code>&quot;World&quot;</code>. <strong>That&rsquo;s pretty incredible compared to the nothing we knew about this collection of bytes a second ago!</strong> Let me quickly explain protobuf field numbers. The <code>1</code> in this example is a field number and it corresponds to the protobuf <a href="https://protobuf.com/docs/language-spec#field-numbers" rel="external">field number</a>. For repeated values, you may see this number appear multiple times. But notice how the name is completely missing. That&rsquo;s because protobuf doesn&rsquo;t want to waste resources transmitting or storing metadata like that. It would be fair to summarize protobuf as a list of key/value pairs. The key is the field number and the values are one of the basic types in protobuf.</p>
<p>For the record, here&rsquo;s what <a href="https://buf.build/connectrpc/eliza/docs/main:connectrpc.eliza.v1#connectrpc.eliza.v1.SayRequest" rel="external">connectrpc.eliza.v1.SayRequest</a> looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">SayRequest</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>	<span style="color:#81a1c1">string</span> sentence <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><blockquote>
<p>Disclaimer: Protoscope has to guess types in a lot of instances because the protobuf encoding has only 6 wire types (two deprecated): <code>VARINT</code>, <code>I64</code>, <code>LEN</code>, and <code>I32</code> are used with modern protobuf files. In the example above, field 1 could have been a string, byte array, an embedded message, or packed repeated fields. Protoscope <strong>guessed</strong> that it was a string and showed it to us as a string. It <em>can</em> guess wrong.</p>
</blockquote>
<h3 id="more-strings">More Strings</h3>
<p>Okay, now we&rsquo;re going to look at a message derived from a different type: <a href="https://buf.build/connectrpc/eliza/docs/main:connectrpc.eliza.v1#connectrpc.eliza.v1.IntroduceRequest" rel="external">connectrpc.eliza.v1.IntroduceRequest</a> <code>(protoscope -explicit-wire-types eliza.IntroduceRequest.pb)</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>1:LEN {&#34;Hello World!&#34;}
</span></span></code></pre></div><p>Wait, what? It&rsquo;s the <em>exact</em> same? Yep. If the field numbers and types match there&rsquo;s no distinguishable difference when protobuf is encoded into binary. Here&rsquo;s the protobuf type:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">IntroduceRequest</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>	<span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>Notice the message contains a single string field which is similar to `SayRequest`` above, but there are a few notable differences. The message name and the field name are different but since those two things are never transmitted over the wire with protobuf we can&rsquo;t tell the difference between these two message types without prior knowledge. This flexibility allows you to make certain significant changes to your protobuf file without changing what is encoded&hellip; but you do have to <a href="https://earthly.dev/blog/backward-and-forward-compatibility/" rel="external">follow some rules</a>. These rules make more sense with more knowledge of the protobuf encoding.</p>
<h3 id="bytes">Bytes</h3>
<p>Okay, now let&rsquo;s look at a new type: bytes. Let&rsquo;s take a look at what that looks like with a byte array <code>(protoscope -explicit-wire-types bytes.pb)</code>):</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>4:LEN {
</span></span><span style="display:flex;"><span>  `7365637265745b060fd327e7efb49cb89b479b65b71043859c2bafbd2d712fbcea2a759b230ceed4`
</span></span><span style="display:flex;"><span>  `af7177eef821cd43935bdc74b682aa939ad99379b6a0e9c4e156b42691bf5e7cb7c8194eea230de4`
</span></span><span style="display:flex;"><span>  `8981314872d7286920d6c5d2799546ce6131391ecd75edf27c17f413e257f50f9834454566c3439d`
</span></span><span style="display:flex;"><span>  `7d2e52204aa57ba7`
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>What you&rsquo;re seeing here is a hexadecimal representation of the bytes in our field. In this example, we mostly have random data. But if you pass this hexadecimal text through a hex-to-string converter you may notice that the beginning text, <code>736563726574</code>, decodes to <code>secret</code>. Even though there is ASCII string content in the byte array, protoscope still treats this as a byte array, not a string. Here&rsquo;s a byte array with a string that says <code>Hello World!</code> as the content <code>(protoscope -explicit-wire-types bytes2.pb)</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>4:LEN {&#34;Hello World!&#34;}
</span></span></code></pre></div><p>Wait, what? Protoscope renders the text as text! What gives? Protoscope is, again, guessing the type of the data. It notices that all of the included bytes are in the ASCII range so it renders the content as text. Could this be the wrong thing to do? Maybe!</p>
<h2 id="numbers">Numbers</h2>
<p>Now let&rsquo;s at numbers represented in protobuf. You may see some&hellip; odd things. We&rsquo;ll break it down field by field <code>(protoscope -explicit-wire-types numbers.pb)</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>2:VARINT 3
</span></span><span style="display:flex;"><span>3:VARINT 175
</span></span><span style="display:flex;"><span>5:LEN {`01020304`}
</span></span><span style="display:flex;"><span>7:VARINT 921
</span></span><span style="display:flex;"><span>8:VARINT 1
</span></span><span style="display:flex;"><span>9:I32 1.2345i32   # 0x3f9e0419i32
</span></span><span style="display:flex;"><span>19:LEN {`ffffffffffffffffff01feffffffffffffffff01fdffffffffffffffff01fcffffffffffffffff01`}
</span></span></code></pre></div><p>Refer to this table to see the actual protobuf types and the intended values. You will notice that several of them don&rsquo;t match up with what protoscope outputs at all.</p>
<table>
  <thead>
      <tr>
          <th>Field Number</th>
          <th>Actual Type</th>
          <th>Actual Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2</td>
          <td>enum</td>
          <td>3 (AnEnum.C)</td>
      </tr>
      <tr>
          <td>3</td>
          <td>uint32</td>
          <td>175</td>
      </tr>
      <tr>
          <td>5</td>
          <td>repeated uint64</td>
          <td>1, 2, 3, 4</td>
      </tr>
      <tr>
          <td>7</td>
          <td>int64</td>
          <td>921</td>
      </tr>
      <tr>
          <td>8</td>
          <td>bool</td>
          <td>true</td>
      </tr>
      <tr>
          <td>9</td>
          <td>float</td>
          <td>1.2345</td>
      </tr>
      <tr>
          <td>19</td>
          <td>I32</td>
          <td>-1, -2, -3, -4</td>
      </tr>
  </tbody>
</table>
<ul>
<li><strong>2</strong>: Enum fields are encoded as numbers on the wire. because of this, the name of the enum value may be unknown to you without the protobuf file.</li>
<li><strong>3</strong>: uint32 types look like what you&rsquo;d expect! Nice.</li>
<li><strong>5</strong>: This is the first super weird one. Why is it shown as a string? This has to do with <a href="https://protobuf.dev/programming-guides/encoding/#packed" rel="external">packed repeated fields</a>. The protobuf encoding packs repeated primitive types into a single <code>LEN</code> field (instead of using <code>VARINT</code>, <code>I64</code> or <code>I32</code> as normal). Therefore, protoscope may simply represent this as a string or byte array because it can&rsquo;t tell the difference on the wire.</li>
<li><strong>7</strong>: int64 types also look like what you&rsquo;d expect! Nice.</li>
<li><strong>8</strong>: Booleans are encoded as false = <code>0</code> and true = <code>1</code>.</li>
<li><strong>9</strong>: In this case, protobufs treated the float correctly and we get the correct value.</li>
<li><strong>19</strong>: This has to be the weirdest case. This looks so strange because it&rsquo;s a result of using <code>repeated int32</code> type, <a href="https://protobuf.dev/programming-guides/encoding/#packed" rel="external">which is packed</a> with negative values. Protoscope thinks this value looks like a <code>LEN</code> wire type with binary data in it. It guessed the type incorrectly this time.</li>
</ul>
<h2 id="submessages-and-maps">Submessages and Maps</h2>
<p>In protobuf you can put messages instead of other messages, so let&rsquo;s look at what that looks like from protoscope&rsquo;s perspective <code>(protoscope -explicit-wire-types submessages.pb)</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>6:LEN {
</span></span><span style="display:flex;"><span>  1:LEN {&#34;Harey&#34;}
</span></span><span style="display:flex;"><span>  2:VARINT 1
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>14:LEN {
</span></span><span style="display:flex;"><span>  1:LEN {&#34;buf.build/connectrpc/eliza/connectrpc.eliza.v1.SayRequest&#34;}
</span></span><span style="display:flex;"><span>  2:LEN {1:LEN {&#34;Hello World!&#34;}}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>17:LEN {}
</span></span><span style="display:flex;"><span>18:LEN {1:LEN {&#34;alpha&#34;}}
</span></span><span style="display:flex;"><span>18:LEN {1:LEN {&#34;beta&#34;}}
</span></span></code></pre></div><p>From the protoscope output above you might also notice that we have two field <code>18</code> values. That is because submessages cannot be packed as primitive types can. The &ldquo;unpacked&rdquo; way of representing a repeated value in the protobuf encoding is to simply write the field multiple times with different values. Simple. So field <code>18</code> is likely a repeated submessage field.</p>
<p>Now let&rsquo;s look at maps: <code>(protoscope -explicit-wire-types maps.pb)</code></p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>10:LEN {
</span></span><span style="display:flex;"><span>  1:LEN {&#34;key&#34;}
</span></span><span style="display:flex;"><span>  2:LEN {
</span></span><span style="display:flex;"><span>    1:LEN {&#34;Corgi&#34;}
</span></span><span style="display:flex;"><span>    2:VARINT 1
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>20:LEN {
</span></span><span style="display:flex;"><span>  1:LEN {&#34;Knock, knock&#34;}
</span></span><span style="display:flex;"><span>  2:LEN {&#34;who&#39;s there?&#34;}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>20:LEN {
</span></span><span style="display:flex;"><span>  1:LEN {&#34;Java&#34;}
</span></span><span style="display:flex;"><span>  2:LEN {&#34;Coffee, not code.&#34;}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Wait, what? This looks a lot like submessages! There&rsquo;s a reason for that! Maps ARE submessages in the protobuf encoding. Here&rsquo;s basically what the encoder is doing.</p>
<p>A map that looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">TestWithMap</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  map<span style="color:#eceff4">&lt;</span><span style="color:#81a1c1">string</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1">int32</span><span style="color:#eceff4">&gt;</span> name_to_age <span style="color:#81a1c1">=</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>&hellip; is converted into a submessage that looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">TestWithMap</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">name_to_age_Entry</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">optional</span> <span style="color:#81a1c1">string</span> key <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>    <span style="color:#81a1c1;font-weight:bold">optional</span> <span style="color:#81a1c1">int32</span> value <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">repeated</span> name_to_age_Entry name_to_age <span style="color:#81a1c1">=</span> <span style="color:#b48ead">7</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>This shows that protobuf is a very practical encoding that re-uses basic concepts to support more complex structures.</p>
<h2 id="summary">Summary</h2>
<p>In this blog post, we delved into the intricacies of inspecting binary Protobuf messages using the Protoscope tool. We highlighted its ability to decipher binary data even without the corresponding Protobuf files. We also covered the six wire types used in Protobuf encoding and explored various scenarios involving strings, bytes, numbers, submessages, and maps.</p>
<p>I didn&rsquo;t cover all of the weird edge cases. There are features in the, now deprecated, proto2 format that I didn&rsquo;t show. However, I hope that I&rsquo;ve shown that you can get <em>something</em> from a binary protobuf file. This, alone, is quite impressive for a binary format. You would usually have a very hard time understanding anything without knowledge of the specific binary protocol. This demonstrates how protobufs takes some of the benefits you might get from text-based encodings (composability, support for &ldquo;unknown&rdquo; fields, some amount of discoverability) with the performance of binary formats (speed, reduced size) but protobuf does bring in an extra ingredient: contracts. Because protobuf files are the source of truth for the format and type-safe serialization code, gRPC client code, gRPC server code, and documentation can all be generated from protobuf files this shows the strength of the format&hellip; which is why you should try to never be in a situation where you NEED to use protoscope. You should always have a <a href="https://protobuf.com/docs/descriptors" rel="external">descriptor set</a> or the protobuf files nearby to decode these messages.</p>
<p>For a more extensive overview of the protobuf binary encoding refer to <a href="https://protobuf.dev/programming-guides/encoding/" rel="external">the official documentation</a>.</p>
]]></content:encoded></item><item><title>Introducing protoc-gen-connect-openapi</title><link>https://kmcd.dev/posts/protoc-gen-connect-openapi/</link><pubDate>Tue, 20 Feb 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/protoc-gen-connect-openapi/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/protoc-gen-connect-openapi/cover_hu_666c893d7cc82563.webp" /> &lt;/p>
                
                
                </description><content:encoded><![CDATA[<p><a href="https://connectrpc.com" rel="external">ConnectRPC</a> is a fantastic set of libraries that bridge gRPC into the web. gRPC is no longer relegated to the microservice box. Now it can spread its legs into the browser with gRPC-Web or the Connect protocol. The connect protocol, unlike <a href="https://github.com/grpc/grpc-web" rel="external">gRPC-Web</a>, allows for many standard web tools to work for non-streaming APIs. For unary RPCs Connect exposes an API that is simply JSON over HTTP like you&rsquo;ve seen a million times before so tools like curl, postman, the Javascript Fetch API, etc. all work nicely with Connect. ConnectRPC also provides all three protocols (gRPC, gRPC-Web and Connect) <a href="https://connectrpc.com/docs/multi-protocol/" rel="external">using a single port on a single server</a>. That means that you no longer need proxies to enable gRPC-Web and all standard gRPC tools are also at your disposal as well.</p>
<p>Here&rsquo;s what an HTTP request looks like for a unary RPC with connect:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#bf616a">&gt; </span><span style="color:#88c0d0">POST</span> <span style="color:#8fbcbb">/connectrpc.greet.v1.GreetService/Greet</span> <span style="color:#81a1c1;font-weight:bold">HTTP</span><span style="color:#81a1c1">/</span><span style="color:#b48ead">1.1</span>
</span></span><span style="display:flex;"><span><span style="color:#bf616a">&gt;</span> Host: demo.connectrpc.com
</span></span><span style="display:flex;"><span><span style="color:#bf616a">&gt;</span> Content-Type: application/json
</span></span><span style="display:flex;"><span><span style="color:#bf616a">&gt;</span>
</span></span><span style="display:flex;"><span>&gt; {&#34;name&#34;: &#34;Buf&#34;}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt; HTTP/1.1 200 OK
</span></span><span style="display:flex;"><span>&lt; Content-Type: application/json
</span></span><span style="display:flex;"><span>&lt;
</span></span><span style="display:flex;"><span>&lt; {&#34;greeting&#34;: &#34;Hello, Buf!&#34;}
</span></span></code></pre></div><p>If you mark the endpoint as <code>idempotency_level=NO_SIDE_EFFECTS</code> then you can also call <code>GET</code> on the endpoint so the request body has to go into the query parameters. Here&rsquo;s what that looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#bf616a">&gt; </span><span style="color:#88c0d0">GET</span> <span style="color:#8fbcbb">/connectrpc.greet.v1.GreetService/Greet?encoding=json&amp;message=%7B%22name%22%3A%22Buf%22%7D</span> <span style="color:#81a1c1;font-weight:bold">HTTP</span><span style="color:#81a1c1">/</span><span style="color:#b48ead">1.1</span>
</span></span><span style="display:flex;"><span><span style="color:#bf616a">&gt;</span> Host: demo.connectrpc.com
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt; HTTP/1.1 200 OK
</span></span><span style="display:flex;"><span>&lt; Content-Type: application/json
</span></span><span style="display:flex;"><span>&lt;
</span></span><span style="display:flex;"><span>&lt; {&#34;greeting&#34;: &#34;Hello, Buf!&#34;}
</span></span></code></pre></div><p>It&rsquo;s that&hellip; simple? Elegant? No? If you&rsquo;re not sold on ConnectRPC now then this may not be the post for you because the rest will talk about my new tool created for ConnectRPC; <strong><a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">protoc-gen-connect-openapi</a></strong>.</p>
<h2 id="introducing-protoc-gen-connect-openapi">Introducing protoc-gen-connect-openapi</h2>
<p><a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">protoc-gen-connect-openapi</a> generates OpenAPI v3.1 files from protobuf files that match the API that the <a href="https://connectrpc.com/docs/protocol" rel="external">Connect protocol</a> exposes.</p>
<p>We can document the Connect API as if it&rsquo;s a real JSON/HTTP API&hellip; because it is, and the gRPC &ldquo;flavor&rdquo; isn&rsquo;t so noticable due to Connect. With <a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">protoc-gen-connect-openapi</a> you can declare your API using protobuf, serve it using gRPC/gRPC-WEb/Connect and fully document it without the API consumers ever knowing what protobuf is or how to read it. To me, this is the best of all worlds.</p>
<p>So, specifically, what good are OpenAPI files generated for Connect? What does that do? Well, first of all, it allows you to generate documentation with tools like <a href="https://github.com/Redocly/redocly-cli" rel="external">redocly</a> or <a href="https://github.com/stoplightio/elements" rel="external">elements</a>.</p>

<img src="https://kmcd.dev/posts/protoc-gen-connect-openapi/screenshot_hu_9cb348a5b1695378.webp"
        alt="Screenshot of protoc-gen-connect-openapi with redocly"/>
<p>Additionally, with tools like <a href="https://openapi-generator.tech" rel="external">OpenAPI-Generator</a> you can generate API clients for languages that Connect doesn&rsquo;t support yet (for non-streaming methods).</p>
<div class="container">
  <pre class="mermaid">flowchart LR

protobuf(Protobuf) -->|protoc-gen-connect-openapi| openapi(OpenAPI)
openapi -->|elements| docs(Gorgeous\nAPI Documentation)
openapi -->|redocly| docs
openapi -->|openapi-generator| other-languages(Languages that\nConnect doesn't\n support yet)
openapi -->|other tool| ???(Something equally amazing)
click elements "https://github.com/stoplightio/elements" _blank
click openapi-generator "https://github.com/OpenAPITools/openapi-generator" _blank
  </pre>
</div>
<p>In summary, I hope this tool will extend the contract-based usage of protobuf along with ConnectRPC&rsquo;s RPC-based API into further places with more documentation, more code generation, and additional validation.</p>
<p>For more information, check out the <a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">protoc-gen-connect-openapi repo on Github</a>.</p>
]]></content:encoded></item><item><title>gRPC From Scratch: Part 2 - Server</title><link>https://kmcd.dev/posts/grpc-from-scratch-part-2/</link><pubDate>Sat, 17 Feb 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/grpc-from-scratch-part-2/</guid><description><![CDATA[ 
                <p> <img hspace="5" src="https://kmcd.dev/posts/grpc-from-scratch-part-2/cover_hu_c3651c64820de15d.webp" /> </p>
                
                Last part we created a simple gRPC client. Let&#39;s take it a bit further. Let&#39;s implement a simple gRPC server in go.
                ]]></description><content:encoded><![CDATA[<p>Last time we made a super simple gRPC client. <strong>This time we&rsquo;re going to make a gRPC server</strong>. We are going to completely reuse the <a href="https://kmcd.dev/posts/grpc-from-scratch/#encoding-the-request">writeMessage</a> and <a href="https://kmcd.dev/posts/grpc-from-scratch/#decoding-the-response">readMessage</a> from <a href="https://kmcd.dev/posts/grpc-from-scratch/">last time</a> because they work the same on the server. After all, the envelope for servers is the same as the envelope for clients. Sweet!</p>
<h2 id="the-setup">The Setup</h2>
<p>Like last time, we&rsquo;re going to use <a href="https://connectrpc.com/docs/go/getting-started" rel="external">ConnectRPC</a> to help us test our implementation. Last time we used the ConnectRPC&rsquo;s server to test our custom gRPC client so this time we&rsquo;re going to use the ConnectRPC&rsquo;s client to test our custom gRPC server. Did I say that right? Yeah, I think so&hellip; Let&rsquo;s move on. Here&rsquo;s what the full client looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;context&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;crypto/tls&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;connectrpc.com/connect&#34;</span>
</span></span><span style="display:flex;"><span>	greetv1 <span style="color:#a3be8c">&#34;github.com/sudorandom/kmcd.dev/grpc-from-scratch-part-2/gen&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;github.com/sudorandom/kmcd.dev/grpc-from-scratch-part-2/gen/greetv1connect&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;golang.org/x/net/http2&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	httpClient <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>http<span style="color:#eceff4">.</span>Client<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Transport<span style="color:#eceff4">:</span> <span style="color:#81a1c1">&amp;</span>http2<span style="color:#eceff4">.</span>Transport<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>			AllowHTTP<span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">true</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>			DialTLSContext<span style="color:#eceff4">:</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>ctx context<span style="color:#eceff4">.</span>Context<span style="color:#eceff4">,</span> network<span style="color:#eceff4">,</span> addr <span style="color:#81a1c1">string</span><span style="color:#eceff4">,</span> _ <span style="color:#81a1c1">*</span>tls<span style="color:#eceff4">.</span>Config<span style="color:#eceff4">)</span> <span style="color:#eceff4">(</span>net<span style="color:#eceff4">.</span>Conn<span style="color:#eceff4">,</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>				<span style="color:#81a1c1;font-weight:bold">return</span> net<span style="color:#eceff4">.</span><span style="color:#88c0d0">Dial</span><span style="color:#eceff4">(</span>network<span style="color:#eceff4">,</span> addr<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>			<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>		<span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	client <span style="color:#81a1c1">:=</span> greetv1connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewGreetServiceClient</span><span style="color:#eceff4">(</span>httpClient<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;http://127.0.0.1:9000&#34;</span><span style="color:#eceff4">,</span> connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">WithGRPC</span><span style="color:#eceff4">())</span>
</span></span><span style="display:flex;"><span>	req <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>greetv1<span style="color:#eceff4">.</span>GreetRequest<span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;World&#34;</span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;send-&gt; %v\n&#34;</span><span style="color:#eceff4">,</span> req<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	resp<span style="color:#eceff4">,</span> err <span style="color:#81a1c1">:=</span> client<span style="color:#eceff4">.</span><span style="color:#88c0d0">Greet</span><span style="color:#eceff4">(</span>context<span style="color:#eceff4">.</span><span style="color:#88c0d0">Background</span><span style="color:#eceff4">(),</span> connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewRequest</span><span style="color:#eceff4">(</span>req<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">if</span> err <span style="color:#81a1c1">!=</span> <span style="color:#81a1c1;font-weight:bold">nil</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatalf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;err: %s&#34;</span><span style="color:#eceff4">,</span> err<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Printf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;recv&lt;- %v\n&#34;</span><span style="color:#eceff4">,</span> resp<span style="color:#eceff4">.</span>Msg<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>And as for more setup, here&rsquo;s some of the more boring parts of the server:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	mux <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServeMux</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">Handle</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;/greet.v1.GreetService/Greet&#34;</span><span style="color:#eceff4">,</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandlerFunc</span><span style="color:#eceff4">(</span>greetHandler<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatal</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServe</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a3be8c">&#34;localhost:9000&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		h2c<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewHandler</span><span style="color:#eceff4">(</span>mux<span style="color:#eceff4">,</span> <span style="color:#81a1c1">&amp;</span>http2<span style="color:#eceff4">.</span>Server<span style="color:#eceff4">{}),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>Here we create an HTTP server <a href="https://connectrpc.com/docs/go/deployment/#h2c" rel="external">with h2c so we aren&rsquo;t required to use TLS for these examples</a>, mount an HTTP path for our one endpoint and start the server. The real fun happens in <code>greetHandler</code>. But before I show that, I need to talk about HTTP trailers.</p>
<h2 id="http-trailers">HTTP Trailers</h2>
<p>Trailers are the same idea as headers but they happen after the response instead of before. Since gRPC is a streaming protocol it uses trailers to report on the overall status of the request instead of the HTTP status code. Go supports sending trailers. I am having a hard time coming up with a good explanation of how it works, so here&rsquo;s an example:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	mux <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServeMux</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">HandleFunc</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;/sendstrailers&#34;</span><span style="color:#eceff4">,</span> <span style="color:#81a1c1;font-weight:bold">func</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> req <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		w<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">Set</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Trailer&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;MessagesSent&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		w<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteHeader</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>StatusOK<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>        io<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteString</span><span style="color:#eceff4">(</span>w<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;Hello world!&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>		w<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">Set</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;MessagesSent&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;100&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>In this example, a trailer named <code>MessagesSent</code> will be sent AFTER the <code>Hello world!</code> body has been sent to the client. Notice how we set a header called <code>Trailer</code> at the beginning with <code>MessagesSent</code> as the value? Yeah, that&rsquo;s just how you do it in Go. It&rsquo;s <a href="https://pkg.go.dev/net/http#example-ResponseWriter-Trailers" rel="external">super strange</a>. A more complete explanation exists in the <a href="https://pkg.go.dev/net/http#ResponseWriter" rel="external">Go documentation</a> where there exists a separate equally magical way to add trailers that is less preferred for an unspecified reason:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// There are two ways to set Trailers. The preferred way is to</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// predeclare in the headers which trailers you will later</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// send by setting the &#34;Trailer&#34; header to the names of the</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// trailer keys which will come later. In this case, those</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// keys of the Header map are treated as if they were</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// trailers. See the example. The second way, for trailer</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// keys not known to the [Handler] until after the first [ResponseWriter.Write],</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// is to prefix the [Header] map keys with the [TrailerPrefix]</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// constant value.</span>
</span></span></code></pre></div><p>Why doesn&rsquo;t Go support a more&hellip; normal interface for trailers? <a href="https://go-review.googlesource.com/c/go/&#43;/2157" rel="external">Who knows.</a>. Trailers are often a bit of an afterthought. That is also proven by the fact that the &ldquo;Trailer&rdquo; values <a href="https://pkg.go.dev/net/http#Request" rel="external">available in Go&rsquo;s http.Request</a> uses the type <code>http.Headers</code>. Oof.</p>
<h2 id="implementing-the-greet-handler">Implementing the Greet Handler</h2>
<p>As I did last time, I&rsquo;m omitting error handling for clarity since <code>readMessage</code> and <code>writeMessage</code> can both return an error. The code that I end up with <a href="https://github.com/sudorandom/kmcd.dev/tree/main/content/posts/2024/grpc-from-scratch-part-2/go/server/main.go" rel="external">does handle errors that might happen when reading or writing the HTTP body</a>. However, take a look at the cleaner version first:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">greetHandler</span><span style="color:#eceff4">(</span>w http<span style="color:#eceff4">.</span>ResponseWriter<span style="color:#eceff4">,</span> r <span style="color:#81a1c1">*</span>http<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	w<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">Set</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Trailer&#34;</span><span style="color:#eceff4">,</span> gRPCStatusHeader<span style="color:#81a1c1">+</span><span style="color:#a3be8c">&#34;, &#34;</span><span style="color:#81a1c1">+</span>gRPCMessageHeader<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	w<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">Set</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Content-Type&#34;</span><span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;application/grpc+proto&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	w<span style="color:#eceff4">.</span><span style="color:#88c0d0">WriteHeader</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span>StatusOK<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">defer</span> r<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">.</span><span style="color:#88c0d0">Close</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	req <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>greetv1<span style="color:#eceff4">.</span>GreetRequest<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>	<span style="color:#88c0d0">readMessage</span><span style="color:#eceff4">(</span>r<span style="color:#eceff4">.</span>Body<span style="color:#eceff4">,</span> req<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#88c0d0">writeMessage</span><span style="color:#eceff4">(</span>w<span style="color:#eceff4">,</span> <span style="color:#81a1c1">&amp;</span>greetv1<span style="color:#eceff4">.</span>GreetResponse<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Greeting<span style="color:#eceff4">:</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sprintf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Hello, %s!&#34;</span><span style="color:#eceff4">,</span> req<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">})</span>
</span></span><span style="display:flex;"><span>	w<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">Set</span><span style="color:#eceff4">(</span>gRPCStatusHeader<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;0&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	w<span style="color:#eceff4">.</span><span style="color:#88c0d0">Header</span><span style="color:#eceff4">().</span><span style="color:#88c0d0">Set</span><span style="color:#eceff4">(</span>gRPCMessageHeader<span style="color:#eceff4">,</span> <span style="color:#a3be8c">&#34;&#34;</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><h2 id="putting-it-all-together">Putting it all together</h2>
<p>Surprisingly, this is pretty much all of the parts that are needed to handle a unary gRPC RPC. You can see <a href="https://github.com/sudorandom/kmcd.dev/tree/main/content/posts/2024/grpc-from-scratch-part-2/go" rel="external">the entire working prototype here</a>.</p>
<h3 id="logs-from-the-client">Logs from the client</h3>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>send-&gt; name:&#34;World&#34;
</span></span><span style="display:flex;"><span>recv&lt;- greeting:&#34;Hello, World!&#34;
</span></span></code></pre></div><h3 id="logs-from-the-server">Logs from the server</h3>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>recv&lt;- name:&#34;World&#34;
</span></span><span style="display:flex;"><span>send-&gt; greeting:&#34;Hello, World!&#34;
</span></span></code></pre></div><h3 id="using-buf-curl">Using buf curl</h3>
<p>In addition to using the generating Go code we can also use tools like <code>buf curl</code> to test our server.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ buf curl -v <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           --protocol<span style="color:#81a1c1">=</span>grpc <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           --schema<span style="color:#81a1c1">=</span>greet.proto <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           -d <span style="color:#a3be8c">&#39;{&#34;name&#34;: &#34;World&#34;}&#39;</span> <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           --http2-prior-knowledge <span style="color:#ebcb8b">\
</span></span></span><span style="display:flex;"><span><span style="color:#ebcb8b"></span>           http://127.0.0.1:9000/greet.v1.GreetService/Greet
</span></span><span style="display:flex;"><span>buf: * Invoking RPC greet.v1.GreetService.Greet
</span></span><span style="display:flex;"><span>buf: * Dialing <span style="color:#81a1c1">(</span>tcp<span style="color:#81a1c1">)</span> 127.0.0.1:9000...
</span></span><span style="display:flex;"><span>buf: * Connected to 127.0.0.1:9000
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) POST /greet.v1.GreetService/Greet</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Accept-Encoding: identity</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Content-Type: application/grpc+proto</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Grpc-Accept-Encoding: gzip</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Grpc-Timeout: 119989m</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Te: trailers</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) User-Agent: grpc-go-connect/1.14.0 (go1.21.6) buf/1.29.0</span>
</span></span><span style="display:flex;"><span>buf: &gt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1)</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">}</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) [5 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">}</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) [7 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: * <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Finished upload</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) HTTP/2.0 200 OK</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Content-Length: 20</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Content-Type: application/grpc+proto</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Date: Sat, 17 Feb 2024 07:35:20 GMT</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1)</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">{</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) [5 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: <span style="color:#81a1c1">{</span> <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) [15 bytes data]</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1)</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Grpc-Message:</span>
</span></span><span style="display:flex;"><span>buf: &lt; <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Grpc-Status: 0</span>
</span></span><span style="display:flex;"><span>buf: * <span style="color:#81a1c1">(</span><span style="color:#616e87;font-style:italic">#1) Call complete</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a3be8c">&#34;greeting&#34;</span>: <span style="color:#a3be8c">&#34;Hello, World!&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1">}</span>
</span></span></code></pre></div><p>It works! The <code>[5 bytes data]</code> log lines that you see are the gRPC framing that happens before each message. The <code>[7 bytes data]</code> and <code>[15 bytes data]</code> are the request body and response body respectively. Also, notice the trailers at the end; <code>Grpc-Status: 0</code> indicates that the RPC finished successfully.</p>
<h2 id="how-to-improve">How to improve</h2>
<p>How can we improve the client and server that we&rsquo;ve written? <strong>MANY</strong> details were glossed over when making this client/server. Here are just a small handful:</p>
<ul>
<li>Different encodings (<a href="https://protobuf.dev/programming-guides/proto3/#json" rel="external">JSON</a>)</li>
<li>Compression Support</li>
<li>Support for multiple gRPC status codes</li>
<li>Respecting the status codes at all in the client</li>
<li>Interceptor support</li>
<li>Compile code from protobufs</li>
<li>TLS support</li>
<li>Usage of the <code>Grpc-Timeout</code> header</li>
<li>Retries on error if the method is marked as idempotent</li>
<li>Fix a lot of implementation details
<ul>
<li>For example, our server does not percent-encode the error messages, which should be done according to the gRPC spec</li>
</ul>
</li>
<li><a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md" rel="external">gRPC-Web</a> support</li>
<li><a href="https://connectrpc.com/docs/protocol/" rel="external">ConnectRPC</a> support</li>
</ul>
<p>There is quite a lot that goes into making a real gRPC client/server and a lot of small details that can have a large impact. Many of those details can be found in <a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md" rel="external">the gRPC over HTTP2 spec</a>. Also, if I were making a real client/server I would use a test suite designed for testing a gRPC implementation&rsquo;s conformity to the gRPC spec. <a href="https://github.com/connectrpc/conformance" rel="external">ConnectRPC has one</a>. Using that paired with testing with various gRPC tools and languages while trying to use as many features is probably the best way to validate your client and gRPC server implementations.</p>
<p><a href="https://github.com/sudorandom/kmcd.dev/tree/main/content/posts/2024/grpc-from-scratch-part-2/go" rel="external">See the full prototype from this post here.</a></p>
]]></content:encoded></item><item><title>gRPC From Scratch: Part 1 - Client</title><link>https://kmcd.dev/posts/grpc-from-scratch/</link><pubDate>Thu, 15 Feb 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/grpc-from-scratch/</guid><description><![CDATA[ 
                <p> <img hspace="5" src="https://kmcd.dev/posts/grpc-from-scratch/cover_hu_37c6f2c8533622ad.webp" /> </p>
                
                gRPC is an incredibly popular RPC framework that efficiently connects services. But how does it work? Let&#39;s dive in!
                ]]></description><content:encoded><![CDATA[<blockquote>
<p>Disclaimer: This article is <em>NOT</em> for beginners unfamiliar with gRPC. If you&rsquo;re looking to use gRPC like a sane individual, look elsewhere. Maybe start with <a href="https://grpc.io/docs/" rel="external">the official gRPC documentation</a>.</p>
</blockquote>
<p>In the realm of distributed systems and microservices, <a href="https://grpc.io/" rel="external">gRPC</a> has become the go-to communication protocol, boasting speed and efficiency over other web technologies like JSON-based HTTP APIs. If you&rsquo;ve ever wondered what&rsquo;s happening under the hood of gRPC, you&rsquo;re in luck. Today, we&rsquo;re embarking on an exciting journey into the intricate world of gRPC, delving deep into the byte-level details that make it tick. While I do this, I hope to convince you that gRPC is a <em>much simpler</em> protocol than you probably think.</p>
<p>Today I&rsquo;m focusing on the basics of how the gRPC protocol works from a protocol level. But first, let me tell you what this article is not going to cover: <strong><a href="https://protobuf.dev/" rel="external">protobufs</a></strong>. That is a separate topic that is covered extremely well <a href="https://protobuf.dev/programming-guides/encoding/" rel="external">in the official documentation</a>. I may cover this topic later on in this series but for now, protobufs are being treated as a black box.</p>
<p>The first thing to know is that gRPC is built on top of HTTP. Let&rsquo;s outline how protobuf service definitions map to HTTP/2 semantics with gRPC&hellip; First, let&rsquo;s take this hello world example.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">helloworld</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#616e87;font-style:italic">// The greeting service definition.
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span><span style="color:#81a1c1;font-weight:bold">service</span> Greeter <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#616e87;font-style:italic">// Sends a greeting
</span></span></span><span style="display:flex;"><span><span style="color:#616e87;font-style:italic"></span>  <span style="color:#81a1c1;font-weight:bold">rpc</span> SayHello <span style="color:#eceff4">(</span>HelloRequest<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span> <span style="color:#eceff4">(</span>HelloReply<span style="color:#eceff4">)</span> <span style="color:#eceff4">{}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>Take these simple observations:</p>
<ul>
<li>The package is named <strong>helloworld</strong></li>
<li>The service is named <strong>Greeter</strong></li>
<li>The method is named <strong>SayHello</strong></li>
</ul>
<p>The corresponding HTTP request starts like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#88c0d0">POST</span> <span style="color:#8fbcbb">/helloworld.Greeter/SayHello</span> <span style="color:#81a1c1;font-weight:bold">HTTP</span><span style="color:#81a1c1">/</span><span style="color:#b48ead">2</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#81a1c1">:</span> application/grpc+proto
</span></span></code></pre></div><p>That covers how to start a request. But what does the body look like? Take a look at this table. Sorry, yes: This is binary data. How do you expect gRPC to get its performance improvements if it didn&rsquo;t use a binary encoding??:</p>
<table>
  <thead>
      <tr>
          <th>Byte Offset</th>
          <th>Content</th>
          <th>Decoded</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0</td>
          <td>00000000</td>
          <td>off</td>
          <td>Compressed-Flag</td>
      </tr>
      <tr>
          <td>1:4</td>
          <td>00000000 00000000 00000000 00000111</td>
          <td>7</td>
          <td>Message-Length (Unsigned 32-bit integer; <a href="https://en.wikipedia.org/wiki/Endianness" rel="external">Big Endian ordering</a>)</td>
      </tr>
      <tr>
          <td>5:12</td>
          <td>00001010 00000101 01010111 01101111 01110010 01101100 01100100</td>
          <td>1:&ldquo;World&rdquo;</td>
          <td>Message content</td>
      </tr>
  </tbody>
</table>
<p>There are <strong>5 bytes</strong> in total as a prefix to each encoded protobuf message. The first byte is for a flag saying if the message is compressed or not. Even though headers might say that compression is supported, servers can make their own decisions on whether or not to compress each message. Some messages are just too small to make compression worth it.</p>
<p>The last 4 bytes of the prefix are an unsigned 32-bit integer (<a href="https://en.wikipedia.org/wiki/Endianness" rel="external">using big-endian byte ordering</a>) which tells the client/server how many bytes the next message will take.</p>
<p>gRPC always returns an HTTP 200 status code. That&rsquo;s weird, right? gRPC does this because, for streaming RPCs, it&rsquo;s impossible to know if a request succeeded ahead of time. Therefore, gRPC always returns a 200 as the status code and waits until the very end of the request to report the <code>gRPC status</code> using a lesser-known feature of HTTP called an HTTP trailer. Trailers are exactly like headers but come at the end of a request instead of the beginning.</p>
<blockquote>
<p>Did you know? gRPC doesn&rsquo;t <em>actually</em> require HTTP/2 support. Most HTTP/1.1 servers and proxies lack support for HTTP Trailers even though trailers were <a href="https://www.rfc-editor.org/rfc/rfc7230.html#section-4.4" rel="external">in the HTTP spec since 1.1</a>. You can read more about the full story <a href="https://carlmastrangelo.com/blog/why-does-grpc-insist-on-trailers" rel="external">in this blog post</a>.</p>
</blockquote>
<h2 id="okay-lets-code-something">Okay, let&rsquo;s code something</h2>
<p>For the second half of this article, we&rsquo;re going to build a very un-featureful gRPC client in Go. It won&rsquo;t support many features that are expected out of gRPC but it will be able to make RPC calls.</p>
<h3 id="making-a-service-in-protobuf">Making a service in protobuf</h3>
<p>First, here&rsquo;s the full protobuf file that I&rsquo;m going to use for this example. It&rsquo;s as close to the simplest Hello World in protobufs as you can get.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">package</span> <span style="color:#8fbcbb">greet</span><span style="color:#81a1c1">.</span>v1<span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GreetRequest</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GreetResponse</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> greeting <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">service</span> GreetService <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">rpc</span> Greet<span style="color:#eceff4">(</span>GreetRequest<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span> <span style="color:#eceff4">(</span>GreetResponse<span style="color:#eceff4">)</span> <span style="color:#eceff4">{}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span></code></pre></div><p>I used a <a href="https://github.com/sudorandom/kmcd.dev/tree/main/content/posts/2024-02-15_grpc-from-scratch/go/buf.gen.yaml" rel="external">buf.gen.yaml file</a> along with the <code>buf generate</code> command to build this protobuf into Go types.</p>
<h3 id="making-a-simple-grpc-server">Making a simple gRPC server</h3>
<p>We&rsquo;re not writing the gRPC server from scratch in this example, just a client (but the principles are the same if you want to do this as an exercise on your own). Additionally, we need a real gRPC server to test our client against so I will use the server handler that <a href="https://connectrpc.com/" rel="external">ConnectRPC</a> provides for us. Here&rsquo;s what that looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">import</span> <span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;context&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;connectrpc.com/connect&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;golang.org/x/net/http2&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;golang.org/x/net/http2/h2c&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	greetv1 <span style="color:#a3be8c">&#34;github.com/sudorandom/kmcd.dev/grpc-from-scratch/gen&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a3be8c">&#34;github.com/sudorandom/kmcd.dev/grpc-from-scratch/gen/greetv1connect&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">type</span> GreetServer <span style="color:#81a1c1;font-weight:bold">struct</span><span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#eceff4">(</span>s <span style="color:#81a1c1">*</span>GreetServer<span style="color:#eceff4">)</span> <span style="color:#88c0d0">Greet</span><span style="color:#eceff4">(</span>ctx context<span style="color:#eceff4">.</span>Context<span style="color:#eceff4">,</span> req <span style="color:#81a1c1">*</span>connect<span style="color:#eceff4">.</span>Request<span style="color:#eceff4">[</span>greetv1<span style="color:#eceff4">.</span>GreetRequest<span style="color:#eceff4">])</span> <span style="color:#eceff4">(</span><span style="color:#81a1c1">*</span>connect<span style="color:#eceff4">.</span>Response<span style="color:#eceff4">[</span>greetv1<span style="color:#eceff4">.</span>GreetResponse<span style="color:#eceff4">],</span> <span style="color:#81a1c1">error</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewResponse</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">&amp;</span>greetv1<span style="color:#eceff4">.</span>GreetResponse<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>		Greeting<span style="color:#eceff4">:</span> fmt<span style="color:#eceff4">.</span><span style="color:#88c0d0">Sprintf</span><span style="color:#eceff4">(</span><span style="color:#a3be8c">&#34;Hello, %s!&#34;</span><span style="color:#eceff4">,</span> req<span style="color:#eceff4">.</span>Msg<span style="color:#eceff4">.</span>Name<span style="color:#eceff4">),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">}),</span> <span style="color:#81a1c1;font-weight:bold">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">main</span><span style="color:#eceff4">()</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	greeter <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>GreetServer<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>	mux <span style="color:#81a1c1">:=</span> http<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewServeMux</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span>	path<span style="color:#eceff4">,</span> handler <span style="color:#81a1c1">:=</span> greetv1connect<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewGreetServiceHandler</span><span style="color:#eceff4">(</span>greeter<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	mux<span style="color:#eceff4">.</span><span style="color:#88c0d0">Handle</span><span style="color:#eceff4">(</span>path<span style="color:#eceff4">,</span> handler<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	log<span style="color:#eceff4">.</span><span style="color:#88c0d0">Fatal</span><span style="color:#eceff4">(</span>http<span style="color:#eceff4">.</span><span style="color:#88c0d0">ListenAndServe</span><span style="color:#eceff4">(</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a3be8c">&#34;localhost:9000&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>		h2c<span style="color:#eceff4">.</span><span style="color:#88c0d0">NewHandler</span><span style="color:#eceff4">(</span>mux<span style="color:#eceff4">,</span> <span style="color:#81a1c1">&amp;</span>http2<span style="color:#eceff4">.</span>Server<span style="color:#eceff4">{}),</span>
</span></span><span style="display:flex;"><span>	<span style="color:#eceff4">))</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><h3 id="encoding-the-request">Encoding the Request</h3>
<p>Now that the setup is out of the way, let&rsquo;s build a client that can send a gRPC request and receive a response from the HTTP server. Note that this will only work for a unary RPC call (a call that does not support streaming). First, let&rsquo;s start with encoding/decoding messages using the format I mentioned above.</p>
<p>Here&rsquo;s the code to write a request to a gRPC server.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">writeMessage</span><span style="color:#eceff4">(</span>w io<span style="color:#eceff4">.</span>Writer<span style="color:#eceff4">,</span> msg <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">)</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	prefix <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">make</span><span style="color:#eceff4">([]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">5</span><span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">PutUint32</span><span style="color:#eceff4">(</span>prefix<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">:],</span> <span style="color:#81a1c1">uint32</span><span style="color:#eceff4">(</span><span style="color:#81a1c1">len</span><span style="color:#eceff4">(</span>msg<span style="color:#eceff4">)))</span>
</span></span><span style="display:flex;"><span>	w<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>prefix<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>    w<span style="color:#eceff4">.</span><span style="color:#88c0d0">Write</span><span style="color:#eceff4">(</span>msg<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><h3 id="decoding-the-response">Decoding the Response</h3>
<p>The response is returned using the exact same format as our request. With go, this is what it might look like (without any error handling at all)</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">func</span> <span style="color:#88c0d0">readMessage</span><span style="color:#eceff4">(</span>body io<span style="color:#eceff4">.</span>Reader<span style="color:#eceff4">)</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">byte</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// read the prefix/envelope</span>
</span></span><span style="display:flex;"><span>	prefixes <span style="color:#81a1c1">:=</span> <span style="color:#eceff4">[</span><span style="color:#b48ead">5</span><span style="color:#eceff4">]</span><span style="color:#81a1c1">byte</span><span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>	io<span style="color:#eceff4">.</span><span style="color:#88c0d0">ReadFull</span><span style="color:#eceff4">(</span>body<span style="color:#eceff4">,</span> prefixes<span style="color:#eceff4">[:])</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#616e87;font-style:italic">// Using the message size from the prefix, read that many bytes. That&#39;s our protobuf message.</span>
</span></span><span style="display:flex;"><span>	buffer <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">&amp;</span>bytes<span style="color:#eceff4">.</span>Buffer<span style="color:#eceff4">{}</span>
</span></span><span style="display:flex;"><span>	msgSize <span style="color:#81a1c1">:=</span> <span style="color:#81a1c1">int64</span><span style="color:#eceff4">(</span>binary<span style="color:#eceff4">.</span>BigEndian<span style="color:#eceff4">.</span><span style="color:#88c0d0">Uint32</span><span style="color:#eceff4">(</span>prefixes<span style="color:#eceff4">[</span><span style="color:#b48ead">1</span><span style="color:#eceff4">:</span><span style="color:#b48ead">5</span><span style="color:#eceff4">]))</span>
</span></span><span style="display:flex;"><span>	io<span style="color:#eceff4">.</span><span style="color:#88c0d0">CopyN</span><span style="color:#eceff4">(</span>buffer<span style="color:#eceff4">,</span> body<span style="color:#eceff4">,</span> msgSize<span style="color:#eceff4">)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#81a1c1;font-weight:bold">return</span> buffer<span style="color:#eceff4">.</span><span style="color:#88c0d0">Bytes</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>This code reads the prefixes (the compression flag and the message size) and then the message size is used when reading the message from the server. That&rsquo;s&hellip; essentially it.</p>
<h2 id="the-rest">The rest</h2>
<p>We have the foundation of the gRPC protocol completed. Now, the missing part is code that creates the actual HTTP request, encoding/decoding the actual protobuf types (which is a simple call to <code>proto.Marshal</code> and <code>proto.Unmarshal</code>) and doing some error handling that I didn&rsquo;t do above. To me, none of that is particularly interesting but you can explore <a href="https://github.com/sudorandom/kmcd.dev/tree/main/content/posts/2024/grpc-from-scratch/go" rel="external">the entire working prototype here</a> on your own time.</p>
<p>Here&rsquo;s what the output looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>send-&gt; name:&#34;World&#34;
</span></span><span style="display:flex;"><span>recv&lt;- greeting:&#34;Hello, World!&#34;
</span></span></code></pre></div><h2 id="what-about-grpc-streams">What about gRPC streams?</h2>
<p>Streaming requests simply repeat this envelope encoding. gRPC, for better or worse, made the unary (simple request and response) use case a bit harder for the benefit of making the more complex use cases (streams) simple. Plus, you only have to write one encoder/decoder. For a server streaming RPC, you would simply repeat the <code>readMessage()</code> function call until you get an EOF or some other error. This showcases gRPC&rsquo;s simplicity in handling streaming.</p>
<p>Note that you will have to periodically call <code>Flush()</code> on the handler&rsquo;s <code>http.ResponseWriter</code> argument to get the bytes flushed to the TCP socket. This normally happens for you automatically after the <code>http.Handler</code> is complete&hellip; but with streaming calls we have to do this ourselves! Here&rsquo;s an example of how to do it:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#81a1c1;font-weight:bold">if</span> f<span style="color:#eceff4">,</span> ok <span style="color:#81a1c1">:=</span> w<span style="color:#eceff4">.(</span>http<span style="color:#eceff4">.</span>Flusher<span style="color:#eceff4">);</span> ok <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>	f<span style="color:#eceff4">.</span><span style="color:#88c0d0">Flush</span><span style="color:#eceff4">()</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><blockquote>
<p>By the way, <a href="https://connectrpc.com/docs/protocol" rel="external">ConnectRPC</a> has chosen to &ldquo;undo&rdquo; this approach by offering unary RPC without the 5-byte custom envelope. This makes using tools like cURL possible and even pleasant, especially when using the JSON encoding. But it is also friendly with gRPC clients <a href="https://connectrpc.com/docs/multi-protocol/" rel="external">by offering gRPC and gRPC-Web alongside the Connect protocol</a>.</p>
</blockquote>
<h2 id="okay-what-was-the-point">Okay, what was the point?</h2>
<p>Hopefully, I was able to shed a little bit of light on how gRPC <em>really</em> works. Binary protocols often have hard-to-understand documentation about each byte in a packet. However, gRPC only has 5 bytes of this weirdness so it&rsquo;s a perfect protocol to whet your appetite on network protocols.</p>
<p>If you want to look at more details about the gRPC specification, I would refer you to <a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md" rel="external">the official gRPC specification on GitHub</a>. <a href="https://github.com/sudorandom/kmcd.dev/tree/main/content/posts/2024/grpc-from-scratch/go" rel="external">See the full prototype from this post here.</a></p>
]]></content:encoded></item><item><title>Why you should use gNMI over SNMP in 2024</title><link>https://kmcd.dev/posts/gnmi/</link><pubDate>Sat, 04 Nov 2023 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/gnmi/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/gnmi/cover_hu_3ad0859e01f5c296.webp" /> &lt;/p>
                
                gNMI is better than SNMP and more people need to know about it.
                </description><content:encoded><![CDATA[<p>Network engineers have some unique challenges in monitoring and managing their own network devices. So it may come as a surprise to some people that, by far, the most used protocol for monitoring network devices was created over 30 years ago. However, there is an industry-accepted replacement and it&rsquo;s time to upgrade.</p>
<p><a href="https://en.wikipedia.org/wiki/Simple_Network_Management_Protocol" rel="external">SNMP</a> has been the go-to protocol for network management for decades, but it has some limitations. It is complex, inefficient, and doesn&rsquo;t scale well to modern networks.</p>
<p><a href="https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md" rel="external">gNMI</a> is a newer protocol that is designed to address these limitations. It is simpler, more efficient, and more scalable than SNMP. It is also more flexible, giving network administrators more control over what data they collect and how they collect it.</p>
<p>gNMI has the following operations:</p>
<ul>
<li><strong>Get</strong> is used to retrieve data from network devices.</li>
<li><strong>Set</strong> is used to modify data on network devices.</li>
<li><strong>Subscribe</strong> is used to receive updates from network devices when the data changes.</li>
</ul>
<h3 id="why-is-gnmi-better-than-snmp">Why is gNMI better than SNMP?</h3>
<p>Here are a few specific reasons:</p>
<ul>
<li><strong>gNMI is model-driven.</strong> This means that it uses <a href="https://datatracker.ietf.org/doc/html/rfc6020" rel="external">YANG</a> to define the data that can be collected from network devices. This makes it easier to write and maintain scripts and applications that collect and manage network data.</li>
<li><strong>gNMI is bidirectional.</strong> This means that it can be used to both collect data from network devices and send data to them. This makes it possible to use gNMI for a wider range of network management tasks, such as configuring devices and troubleshooting problems.</li>
<li><strong>gNMI is efficient.</strong> gNMI uses a streaming protocol to collect data from network devices. This means that it can collect data more efficiently than SNMP, which uses a polling-based approach.</li>
<li><strong>gNMI is scalable.</strong> gNMI is designed to scale to large networks with many devices. It can handle a high volume of traffic without sacrificing performance.</li>
</ul>
<h4 id="subscriptions">Subscriptions</h4>
<p>Let me elaborate on the &ldquo;streaming protocol&rdquo; comment. gNMI allows for a sustained streaming connection where the server can send updates to the client. This is possible because <a href="https://grpc.io/docs/what-is-grpc/core-concepts/#server-streaming-rpc" rel="external">gNMI uses gRPC</a>. SNMP has no such streaming ability[0] and most SNMP communication happens with a simple request/response pattern. Let&rsquo;s look at what a typical SNMP-based monitoring setup looks like:</p>
<div class="container">
  <pre class="mermaid">---
title: Polling for interface statistics with SNMP
---
sequenceDiagram
    participant client AS SNMP Client
    participant device AS SNMP Device

    client->>device: Get Interface Statistics
    device->>client: Interface Statistics: Ethernet1/inOctets = 1000000

    client->>device: Get Interface Statistics
    device->>client: Interface Statistics: Ethernet1/inOctets = 1000000

    client->>device: Get Interface Statistics
    device->>client: Interface Statistics: Ethernet1/inOctets = 1000000

    client->>device: Get Interface Statistics
    device->>client: Interface Statistics: Ethernet1/inOctets = 1000000

    client->>device: Get Interface Statistics
    device->>client: Interface Statistics: Ethernet1/inOctets = 1000000

    client->>device: Get Interface Statistics
    device->>client: Interface Statistics: Ethernet1/inOctets = 1000000

    client->>device: Get Interface Statistics
    device->>client: Interface Statistics: Ethernet1/inOctets = 1000420
  </pre>
</div>
<p>Notice that the &ldquo;Get Interface Statistics&rdquo; request is repeated over and over again. The SNMP client has to keep asking over and over again for updates and will get back the same data it just got a minute ago. Also, I&rsquo;m masking some serious ugliness of SNMP for your sake. With SNMP, you have to look at a table to map an index number to the interface name&hellip; And if an interface is &ldquo;too fast&rdquo; then you may need to poll the <a href="https://datatracker.ietf.org/doc/html/rfc2233#section-3.1.6" rel="external">ifHCInOctets</a> value instead. Neither of these issues exists in gNMI.</p>
<p>Now let&rsquo;s see how gNMI handles this situation using so-called subscriptions:</p>
<div class="container">
  <pre class="mermaid">---
title: Subscribing to interface statistics with gNMI
---
sequenceDiagram
    participant client AS gNMI Client
    participant device AS gNMI Device

    client->>device: Subscribe To Interface Statistics
    device->>client: Subscription established

    device->>client: /interfaces/interface[name=Ethernet1]/state/counters/in-octets = 1000000

    device->>client: /interfaces/interface[name=Ethernet1]/state/counters/in-octets = 1000420
  </pre>
</div>
<p>In this example, we set up the subscription once and from then on we get updates to these counters. Note that we ONLY get updates. This is a strength that&rsquo;s possible in gNMI. We can choose to not get updates if nothing has changed with a piece of data, so if an interface counter has not changed then we just won&rsquo;t get an update for that interface. This shows how gNMI subscriptions reduce the need for a large number of polling requests which has the benefit of reducing the load on network devices. Said differently, instead of polling devices for updates at regular intervals, gNMI subscriptions can allow devices to send updates to clients only when the data changes.</p>
<h4 id="more">More</h4>
<p>In addition to the above, here are some other benefits of using gNMI:</p>
<ul>
<li><strong>gNMI is more secure than SNMP.</strong> Since it is based on HTTP/2, it uses TLS to encrypt all traffic, which helps to protect your network from unauthorized access.</li>
<li><strong>gNMI is supported by a growing number of network devices and management tools.</strong> This makes it easier to find the solutions that you need to meet your specific needs. Tools like <a href="https://gnmic.openconfig.net/" rel="external">gNMIc</a> make it much easier to peer into the state of devices while offering a much better user experience than tools like <code>snmpget</code> or <code>snmpwalk</code>.</li>
<li><strong>gNMI is an open standard.</strong> This means that it is not controlled by any one vendor, which gives you more flexibility and choice. Even when the data models that are used aren&rsquo;t OpenConfig, YANG is still almost always used to describe the vendor-specific data model and makes documentation and automation infinitely easier.</li>
</ul>
<p>Overall, gNMI is a more modern and efficient approach to network management than SNMP. It is a good choice for organizations of all sizes, from small businesses to large enterprises. I have a hypothesis that gNMI could also be good for smaller-scale (maybe homelab?) setups as well, but more on that in a future post. In the future, I also want to dive into some details of gNMI with: different types of subscriptions: (<a href="https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#35152-stream-subscriptions" rel="external">STREAM</a>, <a href="https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#35153-poll-subscriptions" rel="external">POLL</a>, <a href="https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#35151-once-subscriptions" rel="external">ONCE</a>), <code>updates_only</code>/<code>sync_response</code>, coalescing of duplicated updates, dial out with <a href="https://github.com/openconfig/grpctunnel" rel="external">grpctunnel</a>, and core data types. However, I&rsquo;m low on time and each of these topics deserves separate posts. Until then, thanks for reading!</p>
<hr>
<h4 id="footnotes">Footnotes</h4>
<ul>
<li><em><strong>[0]</strong> Yes, I know SNMP traps exist but they&rsquo;re not applicable in this and many other examples and it&rsquo;s also a pain to work with, may silently break and is overall awful to work with.</em></li>
</ul>
<h4 id="references">References</h4>
<ul>
<li><a href="https://github.com/openconfig/gnmi" rel="external">gNMI on github</a> - has a reference client/server in Go.</li>
<li><a href="https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md" rel="external">gNMI specification</a> - the specification documentation for gNMI</li>
<li><a href="https://en.wikipedia.org/wiki/Simple_Network_Management_Protocol" rel="external">SNMP on Wikipedia</a> - SNMP has many different RFCs (because it&rsquo;s old) so Wikipedia is a source for the full list.</li>
<li><a href="https://datatracker.ietf.org/doc/html/rfc6020" rel="external">YANG specification</a> - Networking is such a complex problem they made an entirely new language to describe the data.</li>
<li><a href="https://www.openconfig.net/projects/models/" rel="external">OpenConfig YANG models</a> - OpenConfig is the &ldquo;de facto&rdquo; models for network devices and is a good example of using YANG itself.</li>
<li><a href="https://gnmic.openconfig.net/" rel="external">gNMIc</a> - Commandline tool that supports many gNMI features and supports data transformation, path suggestions based on YANG, dial-out telemetry, multiple gNMI targets, and a lot more.</li>
</ul>
]]></content:encoded></item><item><title>The Rollercoaster of Productivity in Side Projects</title><link>https://kmcd.dev/posts/productivity/</link><pubDate>Sun, 10 Sep 2023 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/productivity/</guid><description> 
                
                Frank talk about the rollercoaster of productivity.
                </description><content:encoded><![CDATA[<p>Okay, let&rsquo;s talk.</p>
<p>I had a super productive period on this blog a few weeks ago. I thought nothing could stop me from updating this blog. I had lots of ideas, lots of motivation, and a lot of energy to write these posts. Even so, I&rsquo;m blocked. I feel like life has gotten in the way of doing what I like to do with this blog. I&rsquo;ve been sick, I&rsquo;ve had work travel, I&rsquo;ve had a minor surgery. I can come up with a lot of reasons for why I&rsquo;ve dropped off with updating this blog but I can&rsquo;t answer why I haven&rsquo;t just started up again. What I&rsquo;ve learned over time is that my drive to do side projects follows a rollercoaster pattern. For the rest of this post, I&rsquo;m going to overview some ideas that I have to cope with this. This is mostly reminders for me, but hopefully, you can get some use out of this as well.</p>
<h2 id="queue-updates">Queue Updates</h2>
<p>I have come to realize that I am not consistent with content generation. I&rsquo;m just not. I will go through phases of hyper-productivity and phases of incredible procrastination. The way I can cope with that is to schedule completed posts in the future. If I have a buffer of updates, it gives me time to roll through the unproductive times without feeling incredibly awful about posting a new update. By the way, all of this pressure is completely self-imposed. I&rsquo;m not sure anyone cares how often I update this thing. I just feel like I should.</p>
<h2 id="finish-things">Finish Things</h2>
<p>Speaking of &ldquo;completed posts&rdquo;, I have several open drafts that are just sitting there, unfinished. As drafts stagnate, I have realized that it&rsquo;s incredibly hard to get myself back into the mindset to pick up a draft and finish it. To deal with this issue, I will try to complete an idea before moving on to a new one. That means that I should either scrap the idea or complete the post. I sometimes hit roadblocks that are entirely dependent on me doing something that I don&rsquo;t want to do. That is a death sentence for a post&hellip; and I need to be more honest with myself in identifying these cases and just dropping the idea. This is a side project. The whole point is that I like doing it&hellip; and if it&rsquo;s pushing me in directions I don&rsquo;t want to go then I should just drop that idea. This isn&rsquo;t my job.</p>
<h2 id="to-the-future">To the future</h2>
<p>I hope to get better at my side-project productivity in the future. Hopefully, some of that was useful to read. It&rsquo;s certainly useful for me to type out. Happy coding and may your side projects thrive, no matter how variable the productivity may be!</p>
]]></content:encoded></item><item><title>Lessons from a Decades-Long Project</title><link>https://kmcd.dev/posts/lessons-from-a-decades-long-project/</link><pubDate>Fri, 18 Aug 2023 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/lessons-from-a-decades-long-project/</guid><description><![CDATA[ 
                <p> <img hspace="5" src="https://kmcd.dev/posts/lessons-from-a-decades-long-project/thumbnail_hu_a93c740512b3a351.webp" /> </p>
                
                Inside Evepraisal&#39;s operations: data automation, databases, deployment, and advice for similar projects.
                ]]></description><content:encoded><![CDATA[<p>I wrote <a href="https://kmcd.dev/posts/evepraisal.com/">Evepraisal</a>. Evepraisal is a tool/website that began as a way to more efficiently blow up transport ships in <a href="https://www.eveonline.com/" rel="external">Eve Online</a> and evolved into a trusted pricing estimate authority for the entire (Eve Online) Universe. This article will cover the technical lessons and experiences that I had maintaining this extremely useful tool for a decade.</p>
<details>
    <summary>
        <strong>Table of Contents</strong> (click to expand)
    </summary>
    <aside>
        <nav id="TableOfContents">
  <ul>
    <li><a href="#getting-started">Getting Started</a>
      <ul>
        <li><a href="#v1-getting-real-with-python">v1: Getting real with Python</a></li>
        <li><a href="#v2-just-rewrite-it-in-go">v2: Just Rewrite it in Go</a>
          <ul>
            <li><a href="#postgresql-to-bolt">PostgreSQL to Bolt</a></li>
            <li><a href="#eve-marketdata-to-esi">Eve-Marketdata to ESI</a></li>
            <li><a href="#other-changes">Other Changes</a></li>
            <li><a href="#data-flow">Data Flow</a></li>
          </ul>
        </li>
      </ul>
    </li>
    <li><a href="#project-recap">Project Recap</a>
      <ul>
        <li><a href="#what-went-wrong">What went wrong</a>
          <ul>
            <li><a href="#unreliable-game-data">Unreliable game data</a></li>
            <li><a href="#cpumemorydisk-constraints">CPU/Memory/Disk Constraints</a></li>
          </ul>
        </li>
        <li><a href="#what-didnt-go-wrong">What didn&rsquo;t go wrong</a>
          <ul>
            <li><a href="#deployment">Deployment</a></li>
            <li><a href="#os-upgrades-and-migrations">OS Upgrades and Migrations</a></li>
            <li><a href="#backuprestore">Backup/restore</a></li>
            <li><a href="#testing">Testing</a></li>
          </ul>
        </li>
        <li><a href="#stats-for-nerds">Stats for nerds</a></li>
      </ul>
    </li>
    <li><a href="#advice">Advice</a>
      <ul>
        <li><a href="#identify-data-dependencies-and-find-good-sources">Identify data dependencies and find good sources</a></li>
        <li><a href="#automate-the-rotation-of-data-dependencies">Automate the rotation of data dependencies</a></li>
        <li><a href="#have-a-backuprestore-plan">Have a backup/restore plan</a></li>
        <li><a href="#cleanup-stale-data-automatically">Cleanup stale data automatically</a></li>
        <li><a href="#get-alerts">Get alerts</a></li>
        <li><a href="#monetization">Monetization</a></li>
        <li><a href="#if-i-were-to-change-things">If I were to change things&hellip;</a></li>
      </ul>
    </li>
    <li><a href="#gf-in-local">gf in local</a></li>
  </ul>
</nav>
    </aside>
</details>

<h2 id="getting-started">Getting Started</h2>
<p>I have already told the story of how it started in my <a href="https://kmcd.dev/posts/economists-with-guns/">Economists with Guns</a> article, but I did want to add that the first version of what became Evepraisal was written in Python and used a static database of pricing data. It also only worked for cargo scan results because it was used for the <a href="https://www.eveonline.com/news/view/observing-the-burn-jita-player-event" rel="external">Burn Jita</a> event in 2012 where players in cheap ships would attack transport ships carrying expensive goods in high-security space knowing that the police would quickly show up and destroy the attackers. I made this tool to help identify which ships were holding expensive goods. The first version had all of the major features: a box you can paste some text into, a listing of all the items and a total price estimate on the top of the page. After using it for a little bit I realized that people <em>didn&rsquo;t believe</em> my estimates when I just posted them in the scouting chat&hellip; So I worked on a way to share the results in the chat with a link, which, I believe, is most of the reason for the success of the website.</p>
<p>After that, life went on. I kind of forgot about the tool. I only intended it to be used for this event so the prices of items never updated. They were all frozen in time. It was a few months later when I noticed that the tool was still being used a good amount and I started getting requests for updated prices and support for other formats. I bought the evepraisal.com domain and started my work.</p>
<h3 id="v1-getting-real-with-python">v1: Getting real with Python</h3>
<p>The next version was written in Python and used an API to fetch market data from eve-marketdata.com, which is now sadly no longer running. Also, the mapping of the item name to the so-called &ldquo;type ID&rdquo; was done by harvesting the list of all types and type IDs from data files that come along with installing the Eve Online client. These files were SQLite databases that could be easily opened and queried. Thankfully, someone else has already done the reverse engineering work and published a <a href="https://github.com/ntt/reverence/" rel="external">Python library</a> to help with querying this data.</p>
<figure><a href="https://kmcd.dev/posts/lessons-from-a-decades-long-project/2013_appraisal.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/lessons-from-a-decades-long-project/2013_appraisal_hu_3535c9bf3ba0f5c5.png"
         alt="What the appraisal page looked like in 2013"/>
    </a>
</figure>

<p>I used <a href="https://www.postgresql.org/" rel="external">PostgreSQL</a> for the database and <a href="https://memcached.org/" rel="external">memcached</a> to cache both appraisal pages and requests to eve-marketdata.com. To get an idea of what the data looks like, here are the tables that this version ended up with.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>Appraisals
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    Id INTEGER
</span></span><span style="display:flex;"><span>    Kind TEXT
</span></span><span style="display:flex;"><span>    RawInput TEXT
</span></span><span style="display:flex;"><span>    Parsed JSON
</span></span><span style="display:flex;"><span>    ParsedVersion INT
</span></span><span style="display:flex;"><span>    Prices JSON
</span></span><span style="display:flex;"><span>    BadLines JSON
</span></span><span style="display:flex;"><span>    Market INTEGER
</span></span><span style="display:flex;"><span>    Created INTEGER
</span></span><span style="display:flex;"><span>    Public BOOL
</span></span><span style="display:flex;"><span>    UserId INTEGER
</span></span></code></pre></div><p>If you&rsquo;re wondering where the &ldquo;type&rdquo; information is, well&hellip; it&rsquo;s in a giant JSON file that is loaded into memory on startup. If you want to see the last version of this file, <a href="https://github.com/evepraisal/python-evepraisal/tree/master/data" rel="external">it exists here</a>.</p>
<figure><a href="https://kmcd.dev/posts/lessons-from-a-decades-long-project/2013.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/lessons-from-a-decades-long-project/2013_hu_9d4ce5bab1a7eb6c.png"
         alt="Evepraisal homepage in 2013"/>
    </a>
</figure>

<p><a href="https://github.com/evepraisal/python-evepraisal/" rel="external">This first version</a> was great but it wasn&rsquo;t perfect. A lot of memory and disk space was being used for the database. The database needed a good amount of maintenance. After a few years, a lot of these things made the website run fairly slow and I had too many outages for my liking, so I needed to do something to help the situation&hellip;</p>
<h3 id="v2-just-rewrite-it-in-go">v2: Just Rewrite it in Go</h3>
<p>To address some of the issues with v1 I decided to <a href="https://github.com/evepraisal/go-evepraisal/" rel="external">rewrite the project in Go</a>. I wanted to experience writing something &ldquo;real&rdquo; in the language so this was it. This effort involved re-writing all of the parsers, all the API handlers, the frontend, and&hellip; <em>everything</em> into Go which took a good deal of effort. But in the end it was worth it. The memory usage was WAY down for several reasons, the CPU usage (which was starting to be a problem too) was very minimal and the website was extremely fast. Rewriting the website in a different language wasn&rsquo;t the only thing that changed though. I changed the database, caching method, how it sourced data, and probably a lot more. All while maintaining a familiar user experience.</p>
<h4 id="postgresql-to-bolt">PostgreSQL to Bolt</h4>
<p>With this rewrite, I also switched from PostgreSQL to an embedded key/value store called <a href="https://github.com/boltdb/bolt" rel="external">Bolt</a>. This means that the only way I have to access data is by the exact primary key or by scanning a range of keys in alphanumeric order. This made some things more difficult but it also taught me a lot about this type of database and it&rsquo;s fast. Extremely fast. I would post the schema here, but that&rsquo;s not a thing for Bolt. So instead, I will show you the buckets and the format I used for keys and values for each bucket.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>bucket=appraisals
</span></span><span style="display:flex;"><span>key_format=&#34;AppraisalID&#34;
</span></span><span style="display:flex;"><span>value=JSON Encoded Appraisal Data
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>bucket=appraisals-last-used
</span></span><span style="display:flex;"><span>key_format=&#34;AppraisalID&#34;
</span></span><span style="display:flex;"><span>value=timestamp encoded as a uint64
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>bucket=appraisals-by-user
</span></span><span style="display:flex;"><span>key_format=&#34;CharacterOwnerHash:AppraisalID&#34;
</span></span><span style="display:flex;"><span>value=AppraisalID
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>bucket=stats
</span></span><span style="display:flex;"><span>key=total_appraisals
</span></span><span style="display:flex;"><span>value=uint64
</span></span></code></pre></div><p>Creating an appraisal would involve adding an entry to <code>appraisals</code>, <code>appraisals-last-used</code>, <code>appraisals-by-user</code> (if the user was logged in) and <code>stats</code> buckets. Deleting is pretty much the same. It took a while to get these features worked out but once it was done I haven&rsquo;t needed to adjust this very much at all.</p>
<h4 id="eve-marketdata-to-esi">Eve-Marketdata to ESI</h4>
<p>I moved from eve-marketdata.com to ESI, which newly supported fetching live(ish) market orders and allowed Evepraisal to have more reliable pricing. ESI also guesses the market price which was a useful value to fall back on but sometimes it was ridiculously out of whack with the market.</p>
<h4 id="other-changes">Other Changes</h4>
<p>Around this time I also fixed some usability issues. I tackled pricing for items that had a super low volume or couldn&rsquo;t be sold on the market by summing up the cost of the components that were needed to build that item. I added a percentage upcharge to that to factor in for build time and effort. In the end, it made for a decent price after some tweaking.</p>
<p>In addition to re-implementing the existing parsers in Go, I also added support for a few more formats and improved on the &ldquo;heuristic&rdquo; parser that would try VERY HARD to find Eve Online types from the input.</p>
<p>The data migration was fairly extensive. I had a process that would re-parse every appraisal again to ensure that it could. I noted differences and eventually decided that it was good enough (or better enough) to do a final migration and flip over to the new site. Because this process was pretty slow I also optimized the parsers a bit at this time so this would go faster. I tested enough to ensure that when the day came that I swapped the website over to the new version most users didn&rsquo;t notice. I made some UI adjustments, which people did notice, but the fact that the entire thing was rewritten was unseen to most users.</p>
<h4 id="data-flow">Data Flow</h4>
<p>So let&rsquo;s put all of this together. Here&rsquo;s the basic dataflow diagram for Evepraisal:</p>
<div class="container">
  <pre class="mermaid">flowchart TD
    esi["ESI\nEvery 6 minutes"]
    pricedb[Prices DB]
    static-data-export["Static Data Export\nEvery 6 hours"]
    typedb[Types DB]

    parser[Parser]    
    pasted-text["Posted Appraisal Text\nFrom a user"]
    appraisal[Appraisal Logic]
    appraisaldb[Appraisal DB]

    appraisal-page[Render and return appraisal page]

    esi --> pricedb
    static-data-export --> typedb

    typedb --> parser
    pasted-text --> parser 
    parser --> appraisal
    pricedb --> appraisal
    appraisal --> appraisaldb
    appraisaldb --> appraisal-page
  </pre>
</div>
<p>And here&rsquo;s what the new version looked like:
<figure><a href="https://kmcd.dev/posts/lessons-from-a-decades-long-project/2023.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/lessons-from-a-decades-long-project/2023_hu_68ff160f37c9fed9.png"
         alt="Evepraisal homepage in 2023"/>
    </a>
</figure>
</p>
<figure><a href="https://kmcd.dev/posts/lessons-from-a-decades-long-project/2023_appraisal.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/lessons-from-a-decades-long-project/2023_appraisal_hu_e4b97af5566e7861.png"
         alt="Appraisal page in 2023"/>
    </a>
</figure>

<p>All-in-all, I think this rewrite went well. It probably took more effort than I expected, but it paid off over the next 6 years from 2017 to a couple of weeks ago. My primary goal was to get a functioning knowledge of operating a project using the Go programming language, and that happened.</p>
<h2 id="project-recap">Project Recap</h2>
<p>So now that I described the evolution of the project and the two major versions, let&rsquo;s recap some major issues and non-issues that I had while maintaining the project.</p>
<h3 id="what-went-wrong">What went wrong</h3>
<p>I encountered hurdles such as unreliable game data, naming inconsistencies, missing data, and disk constraints. Overcoming these challenges shaped how I approach these problems in my other work.</p>
<h4 id="unreliable-game-data">Unreliable game data</h4>
<p>When CCP standardized the names of modules, they never bothered to update the type database with the new names. Also, volumes and packaged volumes are not consistent and not in the game data, so I had to maintain my mapping, which sucked.</p>
<p>Eve-marketdata had a good number of outages and eventually just stopped working for a long period. So to get the website back up I quickly made a few integrations with other services (who I think had less reliable data).</p>
<h4 id="cpumemorydisk-constraints">CPU/Memory/Disk Constraints</h4>
<p>I already mentioned CPU/memory constraints in the motivations to re-write the project using Go. However, v2 still had issues with disk space. Because appraisals were never deleted I was slowly running low on disk space. I ended up fixing this in two ways:</p>
<ul>
<li>changing the default for the API to not store appraisals</li>
<li>purging data more aggressively by using the &ldquo;last seen&rdquo; time to decide which appraisals are unlikely to be looked at again</li>
</ul>
<h3 id="what-didnt-go-wrong">What didn&rsquo;t go wrong</h3>
<p>Certain aspects, like deployment, OS upgrades, and backup/restore, flowed smoothly. Rigorous testing and proactive data management bolstered overall stability.</p>
<h4 id="deployment">Deployment</h4>
<p>You may be surprised to see my deployment script. You can read it in the <a href="https://github.com/evepraisal/go-evepraisal/blob/master/scripts/deploy.sh" rel="external">deploy.sh</a> file. It simply copies up a <a href="https://systemd.io/" rel="external">systemd</a> unit file and the Go binary. Then it ensures that the service is enabled. That&rsquo;s basically it. It&rsquo;s incredible that the deployment only involves updating a single binary file.</p>
<h4 id="os-upgrades-and-migrations">OS Upgrades and Migrations</h4>
<p>Throughout the past decade, there were instances of image upgrades and migrations. Thankfully, these processes transpired without major issues, resulting in only a few minutes of downtime once or twice a year. In the cloud environment, periodic migrations were required to address security vulnerabilities. Notably, cloud providers have improved their strategies, making migrations to new VM host more seamless. An example of a migration that wasn&rsquo;t seamless occurred when the VM hosting Evepraisal was moved to a different data center, requiring some downtime of approximately an hour or two.</p>
<h4 id="backuprestore">Backup/restore</h4>
<p>I never had to restore from backup&hellip; and, to be honest, it’s not the worst thing in the world that the database gets wiped. Sure, it would be annoying to users that have links to their appraisals but a lot of them can be recreated pretty easily.</p>
<h4 id="testing">Testing</h4>
<p>There’s a lot of <a href="https://dave.cheney.net/2019/05/07/prefer-table-driven-tests" rel="external">table-based</a> testing in this project, which allowed me to add new test cases without needing to write a lot of code. This is especially useful in the parser part of the app. Here&rsquo;s an example of a couple of test cases for the &ldquo;listing&rdquo; parser, which handles lines that simply have a quantity and an item name delimited by some kind of whitespace:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;quantities with a decimal, for some reason&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">`123.12	Griffin
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">456.3	Maulus`</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&amp;</span>Listing<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        Items<span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span>ListingItem<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Griffin&#34;</span><span style="color:#eceff4">,</span> Quantity<span style="color:#eceff4">:</span> <span style="color:#b48ead">123</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>            <span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Maulus&#34;</span><span style="color:#eceff4">,</span> Quantity<span style="color:#eceff4">:</span> <span style="color:#b48ead">456</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>        lines<span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">{</span><span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    Input<span style="color:#eceff4">{},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">false</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">},</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;with ending whitespace&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">`Compressed Iridescent Gneiss x 109 `</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&amp;</span>Listing<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        Items<span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span>ListingItem<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Compressed Iridescent Gneiss&#34;</span><span style="color:#eceff4">,</span> Quantity<span style="color:#eceff4">:</span> <span style="color:#b48ead">109</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>        lines<span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">{</span><span style="color:#b48ead">0</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    Input<span style="color:#eceff4">{},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">false</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">},</span> <span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">&#34;with beginning whitespace&#34;</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a3be8c">`1865 Compressed Glossy Scordite
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c"> 105 Compressed Brilliant Gneiss
</span></span></span><span style="display:flex;"><span><span style="color:#a3be8c">  27 Compressed Jet Ochre`</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1">&amp;</span>Listing<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>        Items<span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span>ListingItem<span style="color:#eceff4">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Compressed Brilliant Gneiss&#34;</span><span style="color:#eceff4">,</span> Quantity<span style="color:#eceff4">:</span> <span style="color:#b48ead">105</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>            <span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Compressed Glossy Scordite&#34;</span><span style="color:#eceff4">,</span> Quantity<span style="color:#eceff4">:</span> <span style="color:#b48ead">1865</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>            <span style="color:#eceff4">{</span>Name<span style="color:#eceff4">:</span> <span style="color:#a3be8c">&#34;Compressed Jet Ochre&#34;</span><span style="color:#eceff4">,</span> Quantity<span style="color:#eceff4">:</span> <span style="color:#b48ead">27</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>        lines<span style="color:#eceff4">:</span> <span style="color:#eceff4">[]</span><span style="color:#81a1c1">int</span><span style="color:#eceff4">{</span><span style="color:#b48ead">0</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">,</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#eceff4">},</span>
</span></span><span style="display:flex;"><span>    Input<span style="color:#eceff4">{},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#81a1c1;font-weight:bold">false</span><span style="color:#eceff4">,</span>
</span></span><span style="display:flex;"><span><span style="color:#eceff4">}</span>
</span></span></code></pre></div><p>All of the test cases for all parsers were run by the same code, so adding more tests is just adding a new <code>Case</code> object. Whenever I fixed a reported bug I would create a new test case so that the same bug couldn&rsquo;t happen again. This has prevented an incredible number of potential regressions over the years.</p>
<h3 id="stats-for-nerds">Stats for nerds</h3>
<table>
  <thead>
      <tr>
          <th>Metric</th>
          <th></th>
          <th>Refs</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Apprisals Performed</td>
          <td>150 Million</td>
          <td></td>
      </tr>
      <tr>
          <td>First Commit</td>
          <td>Dec 17, 2012</td>
          <td><a href="https://github.com/evepraisal/python-evepraisal/commit/3a24930edc0566e6c9ca22038a3e6870817497bf" rel="external">3a24930</a></td>
      </tr>
      <tr>
          <td>Last Commit (Python Version)</td>
          <td>Nov 27, 2017</td>
          <td><a href="https://github.com/evepraisal/python-evepraisal/commit/bb949e2c36cf6c10f7e33ea0a8387c0088be71d0" rel="external">bb949e2</a></td>
      </tr>
      <tr>
          <td>First Commit (Go Version)</td>
          <td>Apr 25, 2017</td>
          <td><a href="https://github.com/evepraisal/go-evepraisal/commit/55f6794c8c4b0d3b2e2dbdcecb069232700d6654" rel="external">55f6794</a></td>
      </tr>
      <tr>
          <td>Lines of code (Python)</td>
          <td>1283+1051=2334</td>
          <td><a href="https://github.com/evepraisal/python-evepraisal" rel="external">python-evepraisal</a> + <a href="https://github.com/evepraisal/evepaste" rel="external">eve-paste</a></td>
      </tr>
      <tr>
          <td>Lines of code (Go)</td>
          <td>8022</td>
          <td><a href="https://github.com/evepraisal/go-evepraisal" rel="external">go-evepraisal</a></td>
      </tr>
      <tr>
          <td>Number of Commits</td>
          <td>450+234+130=814</td>
          <td><a href="https://github.com/evepraisal/go-evepraisal" rel="external">go-evepraisal</a> + <a href="https://github.com/evepraisal/python-evepraisal" rel="external">python-evepraisal</a> + <a href="https://github.com/evepraisal/evepaste" rel="external">eve-paste</a></td>
      </tr>
      <tr>
          <td>Total Number of Users</td>
          <td>3,162,712</td>
          <td>Google Analytics (private)</td>
      </tr>
      <tr>
          <td>Total Number of Browser Sessions</td>
          <td>25,251,120</td>
          <td>Google Analytics (private)</td>
      </tr>
      <tr>
          <td>Peak Active Users (single day)</td>
          <td>11,485</td>
          <td>Google Analytics (private)</td>
      </tr>
      <tr>
          <td>Top Countries</td>
          <td>United States, Russia, United Kingdom, Germany, Canada</td>
          <td>Google Analytics (private)</td>
      </tr>
      <tr>
          <td>Gender Breakdown</td>
          <td>93.48% Male, 6.52% Female</td>
          <td>Google Analytics (private)</td>
      </tr>
      <tr>
          <td>Browser Breakdown</td>
          <td>69.31% Chrome, 13.98% Firefox, 6.28% Edge</td>
          <td>Google Analytics (private)</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p>Note that this doesn&rsquo;t cover API access to Evepraisal, so a lot of those usage metrics aren&rsquo;t captured here.</p>
</blockquote>
<figure><a href="https://kmcd.dev/posts/lessons-from-a-decades-long-project/go-evepraisal-commit-history.png"
            
             data-description="Commit history for the Python version of Evepraisal." data-button="See on github" data-button-href="https://github.com/evepraisal/python-evepraisal/graphs/code-frequency"class="spotlight"
            data-preload="true"
            data-download="true"
            data-progress="true"
            data-infinite="true"
            data-spinner="true"
            data-autofit="true"
            data-fit="cover">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/lessons-from-a-decades-long-project/python-evepraisal-commit-history_hu_4683f20f1df8c72a.png"
         alt="Commit history for the Python version of Evepraisal"/>
    </a>
</figure>

<figure><a href="https://kmcd.dev/posts/lessons-from-a-decades-long-project/python-evepraisal-commit-history.png"
            
             data-description="Commit history for the Go version of Evepraisal. Look at how little activity there is!" data-button="See on github" data-button-href="https://github.com/evepraisal/go-evepraisal/graphs/code-frequency"class="spotlight"
            data-preload="true"
            data-download="true"
            data-progress="true"
            data-infinite="true"
            data-spinner="true"
            data-autofit="true"
            data-fit="cover">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/lessons-from-a-decades-long-project/go-evepraisal-commit-history_hu_edffb86e8f2e34cd.png"
         alt="Commit history for the Go version of Evepraisal"/>
    </a>
</figure>

<h2 id="advice">Advice</h2>
<p>Now here&rsquo;s some advice for running a similar project. I feel like most of this applies to many kinds of side-projects that you intend to have people use.</p>
<h3 id="identify-data-dependencies-and-find-good-sources">Identify data dependencies and find good sources</h3>
<p>Evepraisal suffered a lot whenever the source of data wasn&rsquo;t reliable. Eve-marketdata served as the data source for market data for Evepraisal for a long time. I ended up integrating with a similar service as a backup before settling on using CCP&rsquo;s ESI market API to fetch market data from the original source.</p>
<p>It&rsquo;s a similar story with the type database, first, it was gathered using dubious methods that could break often until it was replaced with a more official and reliable data source.</p>
<h3 id="automate-the-rotation-of-data-dependencies">Automate the rotation of data dependencies</h3>
<p>I initially had a script that would scrape Eve Online&rsquo;s type data from the game client data files. This was super manual and it involved me updating the game (which took a lot of my disk storage for a game I didn&rsquo;t play much), running a script, and checking in the new giant JSON file with all of the types in it. The process was &ldquo;fine&rdquo; but it was very reactionary. I had to do this for every major game release and that&rsquo;s pretty often. I would normally get messaged about the game data needing an update from a user noticing a new item didn&rsquo;t get recognized. That&rsquo;s kind of embarrassing, so I needed to improve this.</p>
<p>I was so happy when CCP started regularly releasing the <a href="https://developers.eveonline.com/resource/resources" rel="external">Static Data Export (SDE)</a> for Eve Online. It had (almost) everything I needed. After this change, Evepraisal would periodically check for a new SDE release and, if there was one, would download the new package, process it, make a new type database and switch to using the new one. It was pretty slick once I got this process working reliably.</p>
<p>You may notice a pattern here: moving from scrappy 3rd party APIs that are maintained by individuals to using first-party data sources like SDE and the ESI API. I think CCP did a lot of good work to make it reasonable to create tools like this without a lot of effort.</p>
<h3 id="have-a-backuprestore-plan">Have a backup/restore plan</h3>
<p>I, luckily, never really had an issue with backup and restore, but I do think you need some kind of plan. Users may forgive you if you lose a day&rsquo;s worth of data but they&rsquo;ll be pretty irritated if you lose all of it&hellip; So have a backup. And test the restore process every once in a while. My backup strategy changed a good amount for Evepraisal. I initially took SQL dumps and put them into my Google Drive using a script. However, after switching to Bolt for the database I had to come up with a different strategy. I used a combination of image-level backups along with a script that could export recent appraisals to Google Drive. Later on, I relied solely on the VM image backups.</p>
<h3 id="cleanup-stale-data-automatically">Cleanup stale data automatically</h3>
<p>After you&rsquo;ve gotten the project to a stable state the things that will break are the log files that are never truncated or database tables that grow forever. Come up with a strategy to deal with this. I let systemd manage log files for me. By default, systemd will keep 4GB of log files for you, which was enough for me. I had a subprocess in the Evepraisal server that would clean up old appraisals automatically.</p>
<h3 id="get-alerts">Get alerts</h3>
<p>You need alerts, especially if your interest in the project varies over time. You may have an issue for days before someone tries to tell you so you should be proactive.</p>
<ul>
<li>Log-based alerting
<ul>
<li>I used <a href="https://www.papertrail.com/" rel="external">Papertrail&rsquo;s</a> free plan to handle logging and alerting for me. I filtered a lot of irrelevant things from the log so Papertrail would only receive actionable logs.</li>
</ul>
</li>
<li>Uptime monitoring alerts
<ul>
<li>There are a lot of free services for this but I used <a href="https://uptimerobot.com/" rel="external">Uptime Robot</a>.</li>
</ul>
</li>
<li>Google Alerts
<ul>
<li>Hopefully, the name of your service is unique enough that you can make a <a href="https://www.google.com/alerts" rel="external">Google alert</a> for the name. It will show Reddit threads, blog articles, forum posts, etc. that mention this name. It&rsquo;s great, but it can also be a little slow.</li>
</ul>
</li>
<li>Subscribe to email lists that will tell you if major changes are happening with the APIs that you&rsquo;re using.</li>
<li>Be available on a social platform like Mastodon and list your handle on the website. If people are using your website someone will find your user and tell you if something is wrong if it&rsquo;s broken for long enough.</li>
</ul>
<h3 id="monetization">Monetization</h3>
<p>I used <a href="https://adsense.google.com/" rel="external">Google AdSense</a> to generate revenue from Evepraisal. This revenue completely covered operational costs but it didn&rsquo;t make too much beyond that. The only ongoing costs for this project were hosting and domain name registration. Monetization ensured that the project lived for as long as it did.</p>
<h3 id="if-i-were-to-change-things">If I were to change things&hellip;</h3>
<p>You should keep a log and make an entry for every time your attention is required, you can use that log to figure out what to focus on the next time you feel like working on the project.</p>
<p>I&rsquo;ve always wanted to do more with the tool but life always got in the way. There are so many cool ways to show and process this kind of data. At various times I saw orders that made no sense pop up; orders that I don&rsquo;t think were just margin scans. There would be sell-orders way below market value. Buy orders way above market value. It just made no sense to me because it would happen with high-volume items that had a fairly fixed market price. It didn&rsquo;t happen super often, but you might be able to make some money off of those&hellip; as long as it isn&rsquo;t an obvious scam.</p>
<p>Other than that&hellip; I don&rsquo;t know. I think this was a massively successful project. I&rsquo;m extremely proud to have contributed something so significant to the game.</p>
<h2 id="gf-in-local">gf in local</h2>
<p>This all started from wanting the <a href="https://www.eveonline.com/news/view/observing-the-burn-jita-player-event" rel="external">Burn Jita</a> event to be more streamlined. I saw how chaotic it was when identifying targets and I felt like having a tool to quickly guess the value of the target was very much needed. By the time the next Burn Jita event came around Evepraisal&rsquo;s usage was incredible. Once the event concluded, I came across a post listing all the killmails (records of ships being destroyed) associated with the event. I wrote a script to identify cargo scan appraisals containing items that match those found in the hull of the ships for each of the killmails. I ensured these appraisals were generated <em>before</em> the corresponding kills, suggesting that Evepraisal played a role in these victories. To my immense satisfaction, this script successfully pinpointed every single kill on the list-a truly gratifying accomplishment and a testament to how critical my project has become to the game.</p>
<p>In conclusion, Evepraisal&rsquo;s journey underscores the importance of data reliability, automation, strong testing techniques, and monetization. I thank everyone who has used the tool in their everyday space lives. You kept me going for so long. See <a href="https://kmcd.dev/posts/goodbye-evepraisal/">this post</a> to see more about the reasoning behind shutting the website down.</p>
<p>o7</p>
]]></content:encoded></item><item><title>How I learned to code</title><link>https://kmcd.dev/posts/how-i-learned-to-code/</link><pubDate>Sat, 12 Aug 2023 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/how-i-learned-to-code/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/how-i-learned-to-code/thumbnail_hu_404d0db577e880ce.webp" /> &lt;/p>
                
                This is a small post that discusses how I learned computer programming, from cheating at calculator games to anime fan site to a handmade content management system.
                </description><content:encoded><![CDATA[<p>Writing about this subject is weird to me. I am a programmer. I get paid to turn caffeine into software&hellip; but I feel like I am clueless when it comes to explaining to other people how to get started doing what I do. I learned to program for incredibly silly reasons. Here are a few of my wonderful early projects:</p>
<ul>
<li>cheated at a game on my TI-83 (Falldown without wall detection).</li>
<li>announced my interest in Dragon Ball Z and anime in general with an anime fan website which eventually turned into me creating a database-driven review and article management system.</li>
<li>created a text-based multiplayer game (also spawned from my anime website).</li>
<li>visualized where on the screen I put my cursor while using my computer.</li>
<li>saw the “mood” of Reddit by aggregating the upvotes of top posts and comments of the top subreddits.</li>
<li>many more&hellip; with some I&rsquo;m too ashamed of to put here.</li>
</ul>
<p>Every single one of these projects has been enjoyable and none of it was a direct result of reading a book and following examples. I learned by literally stitching snippets of code together along with A LOT of trial and error. Eventually, I learned how important reference documentation is. Eventually, I learned how to make “real” applications where database passwords aren&rsquo;t sitting in the source code. Eventually, I learned version control. I guess my point here is that there&rsquo;s a lot to learn. Focus on doing something interesting. Solve problems. Create problems. Get messy. Even if you fail (and you will fail <em>a lot</em>) remember that you are learning and this is all part of the process.</p>
<p>Programming is literature. You can&rsquo;t write amazing works after learning about grammar and sentence structure. This will take some time.</p>
]]></content:encoded></item><item><title>Economists with (virtual) Guns</title><link>https://kmcd.dev/posts/economists-with-guns/</link><pubDate>Fri, 11 Aug 2023 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/economists-with-guns/</guid><description> 
                
                This is a story about how I came to create evepraisal.com, a popular tool for Eve Online that is commonly used to price check cargo scans, contracts, EFT fittings, assets, and more. If none of that means anything to you that&amp;#39;s totally fine! I barely know, too.
                </description><content:encoded><![CDATA[<blockquote>
<p>Note: This post was originally posted in 2015. I am re-posting it with some small edits. Updates are at the bottom.</p>
</blockquote>
<p>This is a story about how I came to create evepraisal.com, a popular tool for Eve Online that is commonly used to price check cargo scans, contracts, EFT fittings, assets, and more. If none of that means anything to you that&rsquo;s totally fine! I barely know, too.</p>
<p>Eve Online has always been an interesting game to me. For the uninitiated, Eve Online is a space-themed single-sharded MMO game that boasts an incredibly realistic economy and allows players to take high-stakes risks. This is the game that has had several extremely large fights over player-controlled territories involving thousands of people. It’s a game where politics, espionage and propaganda are effective weapons against your enemy.</p>
<h3 id="burn-jita">Burn Jita</h3>
<p>2 years ago, during a large player-made event called “Burn Jita”, an in-game alliance named Goonswarm and various other null-sec entities sought to blockade the biggest market hub in the game in hopes that the established trading center of Eve Online would be significantly impacted. I happened to be in an alliance comprising “various other null-sec entities” so I participated in the event. How could I not? Here’s how the scheme worked:</p>
<ul>
<li>Designated people would cargo scan ships passing through nearby systems headed toward Jita. The information would be relayed through in-game and out-of-game channels.</li>
<li>A fleet of ships would be waiting to strike juicy targets foolish enough to enter or leave the trade hub with valuable cargo. The ships they were flying were cheap and could deal a lot of instant damage.</li>
<li>Once a transport ship with a significant amount of goods entered the system the fleet would strike. Jita is located in high sec which means every attacking ship would pretty quickly be destroyed by the computer-controlled police force, but the damage output of the entire fleet assured that the transport ship would turn into a pile of wreckage and loot.</li>
<li>Rinse; repeat.</li>
</ul>
<p>Now take a second to appreciate the fact that several of these fleets were operating at any given time and the ships used to do these attacks were provided, free of charge, by each participating alliance.</p>
<p>My role in the process above was being one of the people who did step 1. I did a fair amount of ganking with the fleet but what I really enjoyed was pointing to a person and then waiting 10 minutes to see that his ship turned into a mangled wreck and his goods (or what’s left of them) were stolen to aid the further destruction of people like him. It was my (and my counterpart’s) actions that started the chain of events leading to the death of an enterprising player who paid no attention to player events. The act of firing space bullets was a detail.</p>
<h3 id="necessity-is-the-mother-of-invention">Necessity is the mother of invention</h3>
<p>After a few hours of scanning the cargo of every ship in sight, I realized that many freighters were stuffing their cargo hold with lots of useless junk to hide the true value of the cargo that they were carrying. This was a problem. Deciding who to attack was a time-sensitive task and not having a reliable cost estimate of what the ship contained made things harder than they had to be.</p>
<p>That’s where something clicked in my mind and over the next 3 hours I made a web-based tool that completely solved the ambiguity of this entire process and changed ganking into a legitimate economically-driven career that boasts cost/benefit analysis similar to (yet more exciting than) most of the other more reputable careers in Eve Online. That’s a big claim, but I don’t think I am far off.</p>
<h3 id="let-me-explain">Let me explain…</h3>
<p>When you cargo-scan a potential target’s cargo hold, you get a list of all its contents. Up until recently, all you could do was look at the list and that was it. However, CCP recently added the ability to copy/paste from many different places in the Eve Online UI including the cargo scan result window. Cool, I thought. I could have users to give me a list of all the items in the potential target’s hanger. Now I just needed a way to calculate the prices. Eve Online has an amazing developer community. There were (and are) existing websites that had daily database dumps of aggregate pricing data for every market item in the game. I downloaded one of the database dumps and used that data as my price reference. After that, it was a matter of making a small website where people could give it cargo scan results and it would give back a reasonably accurate guess of the cargo’s value in less than a second. These estimates could be much better than what a human could do manually at the same time.</p>
<p>I randomly linked the website that I had just created in one of the fleet channels and it was a short time later that someone in Goonswarm broadcasted the link to the Goonswarm jabber server. I instantly got a flood of interested people trying out the tool and, to my surprise, the site held up fine. Later, I added the ability to share results so that the many scanners wouldn’t have to communicate the estimate they’re seeing from my website which further streamlined the process. In hindsight, what seemed like a small feature that was fairly easy to implement turned out to be the biggest selling point. The immediate feedback I got from people was invaluable and shaped my thoughts on the problem. To this day I’ve never had a bad experience asking people (even those who didn’t like the tool) what could be better.</p>
<p>The Burn Jita event continued for a couple more days and instead of cargo scanning ships, I was working on improving the tool that others used to find targets to gank more efficiently. The rest is history.</p>
<h3 id="update-2015">Update (2015)</h3>
<p>The website slowly evolved into what is now known as Evepraisal. I added support for many different formats and switched from using price data from a static database dump to using more frequently updated sources like Eve-Central and Eve-marketdata.com. I’ve heard from several people who are career gankers in Eve who leverage Evepraisal to be profitable.</p>
<p>I am still waiting for the day that CCP makes a suitable replacement to my tool, but until then I’ll still be slowly improving on the tool that’s become a ganker’s best friend and a scammer’s worst nightmare.</p>
<h3 id="update-2020">Update (2020)</h3>
<p>It’s sad to say but I’ve let Evepraisal kind of stagnate for a while. It sits in a weird position for me of being an extremely neat facet of my development as a programmer but also it weird to work on because I do not play Eve Online anymore. Since 2015, I’ve made several large changes to Evepraisal that were almost invisible to users. I rewrote the entire app in a different programming language. I sourced the market data from CCP’s own API (that didn’t exist before), which is very lucky because Eve-central eventually completely dissipated. I sourced type information from CCP’s static data dump. After making those two changes the amount of issues Evepraisal has had is extremely small. The requests for new features obviously come in, but I wanted the tool to be targeted at doing one specific thing well. For a while, I hoped that other tools would meet or surpass Evepraisal’s functionality. It took a long time but I believe there are several really good alternatives.</p>
<p>However, there have been two major issues. First, the Google Sheets addon that I made and published became unpublished due to Google completely changing how you administer Google Sheets add-ons. They also required a lot more from developers to improve their storefront… But it was just too many hoops for me to deal with. I attempted but I hit one roadblock too many and gave up. The second issue wasn’t my fault either. To make things more consistent, CCP started changing the names of several items. In the past when this happens it isn’t a big deal since the static data dump that CCP provides always had updates. But ever since the beginning of 2020, CCP has not kept the static data up-to-date with the name changes. It boggles my mind why they would do it this way. I’ve checked and there’s no reference to the new names anywhere in that package of static data files. So, alas, I resorted to actually changing Evepraisal’s code to support type aliases and hard-coded that list of aliases.</p>
<p>So here I am. A lot of stuff is happening in my life and I still don’t want to spend too much time on a 7-year-old project. I do have many, many ideas of how I might want to present market data. Eve Online is still an amazing MMO because of its open APIs and willingness to support third-party developers. And it’s the only MMO that regularly has actual large-scale player-driven events. With all of that said I plan to keep Evepraisal running for as long as people use it.</p>
<h3 id="update-2023">Update (2023)</h3>
<p>I&rsquo;ve decided to shut it down. <a href="https://kmcd.dev/posts/goodbye-evepraisal/">See more about the reasoning here</a> but you can kind of see my attitude 3 years ago.</p>
]]></content:encoded></item><item><title>Visualizing the Internet (2023)</title><link>https://kmcd.dev/posts/internet-map-2023/</link><pubDate>Tue, 01 Aug 2023 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/internet-map-2023/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/internet-map-2023/geo-mercator-apac_hu_5f30212a05023f3e.webp" /> &lt;/p>
                
                Journey into the depths of the Internet with this incredible map showcasing undersea cables and internet exchanges.
                </description><content:encoded><![CDATA[<p>I recently expanded on my <a href="https://kmcd.dev/posts/internet-map-2022/">Internet map visualization</a> that showed all of the undersea internet cables that run along the bottom of the oceans and seas. This time, I also added dots that represent the locations of all of the advertised internet exchanges in the world. The brighter/greener/bigger the dot, the more bandwidth the internet exchange supports.</p>
<figure><a href="https://kmcd.dev/posts/internet-map-2023/geo-mercator.svg"
            
             data-description="This map shows the locations of undersea cables and internet exchanges around the world."class="spotlight"
            data-preload="true"
            data-download="true"
            data-progress="true"
            data-infinite="true"
            data-spinner="true"
            data-autofit="true"
            data-fit="cover">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2023/geo-mercator-small_hu_4aa95cc553e64b84.png"
         alt="Map of the Internet"/>
    </a>
</figure>

<p>And here is the same map <strong>but without country borders</strong>. I think this one looks beautiful:
<figure><a href="https://kmcd.dev/posts/internet-map-2023/nocountrylines_geo-mercator.svg"
            
             data-description="This map shows the locations of undersea cables and internet exchanges around the world, but without land masses."class="spotlight"
            data-preload="true"
            data-download="true"
            data-progress="true"
            data-infinite="true"
            data-spinner="true"
            data-autofit="true"
            data-fit="cover">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2023/nocountrylines_geo-mercator-small_hu_74903f6b256435c3.png"
         alt="Map of the Internet"/>
    </a>
</figure>
</p>
<p><strong><a href="https://kmcd.devgeo-mercator.svg">Click here for full resolution image (warning, it&rsquo;s big)</a></strong></p>
<p><strong><a href="https://kmcd.devnocountrylines_geo-mercator.svg">Click here for full resolution image (no borders) (warning, this one is also big)</a></strong></p>
<h2 id="whats-an-internet-exchange">What&rsquo;s an internet exchange?</h2>
<p><strong>Internet Exchange:</strong></p>
<p>An Internet exchange, often referred to as an Internet Exchange Point (IXP), is a physical infrastructure or facility where Internet Service Providers (ISPs) and other network operators connect their networks together to exchange Internet traffic. The primary purpose of an internet exchange is to improve the efficiency of data exchange between different networks by allowing direct peering between them. Instead of sending data through multiple third-party networks, which can lead to increased latency and costs, ISPs can directly exchange traffic at an internet exchange, resulting in faster and more cost-effective data transmission.</p>
<p>Internet exchanges play a crucial role in the functioning of the global internet. By facilitating direct peering, they help reduce the reliance on expensive long-distance links and enhance the overall performance and resilience of the internet. They also promote competition among ISPs and encourage the growth of internet infrastructure in specific regions.</p>
<p><strong>Traceroute Command:</strong></p>
<p>The <code>traceroute</code> command is a network diagnostic tool used to trace the route that data packets take from one computer to another across a network, typically the Internet. It helps identify the network path taken by the data, showing the sequence of intermediate devices (routers) that the packets pass through before reaching the destination.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ sudo mtr apple.com
</span></span><span style="display:flex;"><span>                            My traceroute  <span style="color:#81a1c1">[</span>v0.95<span style="color:#81a1c1">]</span>
</span></span><span style="display:flex;"><span>kevins-mbp-m1.local <span style="color:#81a1c1">(</span>192.168.88.31<span style="color:#81a1c1">)</span> -&gt; apple.com <span style="color:#81a1c1">(</span>17.22023-08-02T13:19:54+0200
</span></span><span style="display:flex;"><span>Keys:  Help   Display mode   Restart statistics   Order of fields   quit
</span></span><span style="display:flex;"><span>                                      Packets               Pings
</span></span><span style="display:flex;"><span> Host                               Loss%   Snt   Last   Avg  Best  Wrst StDev
</span></span><span style="display:flex;"><span> 1. router.lan                       0.0%     <span style="color:#b48ead">5</span>    5.2   4.8   4.3   5.2   0.4
</span></span><span style="display:flex;"><span> 2. 10.24.0.1                        0.0%     <span style="color:#b48ead">4</span>    4.7   4.6   4.3   5.0   0.3
</span></span><span style="display:flex;"><span> 3. soeborg2.net.gigabit.dk          0.0%     <span style="color:#b48ead">4</span>    5.6   6.6   5.5   9.7   2.1
</span></span><span style="display:flex;"><span> 4. apple-1.equinix-am1.nl-ix.net    0.0%     <span style="color:#b48ead">4</span>   15.6  15.5  14.1  16.6   1.0
</span></span><span style="display:flex;"><span> 5. apple.com.sg                     0.0%     <span style="color:#b48ead">4</span>   15.2  15.0  14.5  15.6   0.5
</span></span></code></pre></div><p>In the provided example output:</p>
<ul>
<li><code>kevins-mbp-m1.local</code> is the name of the source host or the computer from which the <code>traceroute</code> is initiated. That&rsquo;s my laptop, so I know that it is located in Copenhagen, Denmark.</li>
<li><code>apple.com</code> is the destination host or the target server to which the <code>traceroute</code> is being performed.</li>
</ul>
<p>The columns in the output represent the following information:</p>
<ul>
<li><code>Host</code>: The intermediate routers or nodes in the network path.</li>
<li><code>Loss%</code>: The percentage of packet loss experienced while reaching each router.</li>
<li><code>Snt</code>: The number of packets sent to each router.</li>
<li><code>Last</code>: The last round-trip time taken to reach the router.</li>
<li><code>Avg</code>: The average round-trip time to reach the router.</li>
<li><code>Best</code>: The best (minimum) round-trip time to reach the router.</li>
<li><code>Wrst</code>: The worst (maximum) round-trip time to reach the router.</li>
<li><code>StDev</code>: The standard deviation of round-trip times to the router.</li>
</ul>
<p><strong>Identifying Exchange Location using Reverse IP Lookup:</strong></p>
<p>In the <code>traceroute</code> output, you can sometimes infer the location of an internet exchange based on the names of the intermediate routers. Many internet exchanges are named explicitly to indicate their location. For example, in the provided <code>traceroute</code>, the router with the name <code>apple-1.equinix-am1.nl-ix.net</code> suggests that it is associated with the <a href="https://www.equinix.com/data-centers/europe-colocation/netherlands-colocation/amsterdam-data-centers/am1" rel="external">Equinix Amsterdam (AM1) data center</a> connected through the <a href="https://www.nl-ix.net/locations/amsterdam/" rel="external">NL-IX</a> internet exchange. So my request was probably handled inside of <code>Luttenbergweg 4, Amsterdam, Netherlands</code> or&hellip; this building:</p>

<img src="https://kmcd.dev/posts/internet-map-2023/equinix-am1_hu_9572f1ee5adcc78d.webp"
        alt="Equinix Amsterdam (AM1)"/>
<p>However, it&rsquo;s important to note that not all routers or nodes in a traceroute will have such descriptive names. Some routers might be labeled with IP addresses or generic names that do not reveal their location. In such cases, it becomes more challenging to pinpoint the exact location of an internet exchange solely based on the traceroute output. Additionally, the reverse IP lookup method depends on the accuracy and up-to-date information in the IP address registries, and some routers might not be accurately represented.</p>
<p>Because of all of this, we can see the path that is taken when connecting to a server, which I think is really cool. With my example, I can know that it takes around 15.5 milliseconds for my network traffic to reach the Netherlands and I can tell that Apple is peering directly in this facility. You can verify this fact by looking at <a href="https://www.peeringdb.com/ix/2031" rel="external">PeeringDB</a>. Essentially, if you want <code>apple.com</code> to load faster for your users who are near(ish) to Amsterdam, you may want to run some fiber optic cable to this internet exchange. Put another way, the closer you are to the dots the closer you are to the internet.</p>
<h2 id="observations-from-the-map">Observations from the map</h2>
<p>Now here&rsquo;s the pretty part. Here are close-ups of different parts of the map with some general observations.</p>
<figure><a href="https://kmcd.dev/posts/internet-map-2023/geo-mercator-na.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2023/geo-mercator-na_hu_a28fd6aad6122518.png"
         alt="North America" loading="lazy"/>
    </a><figcaption>
            <h4>North America</h4><p>The internet exchanges in North American look how I&rsquo;d imagine them. There are more and higher bandwidth internet exchanges on the eastern side of the US and a good amount along the west coast. However, there are some very important internet exchanges in Colorado. It likely serves as a good halfway point between east and west to transition traffic to different backbone providers.</p>
        </figcaption>
</figure>

<figure><a href="https://kmcd.dev/posts/internet-map-2023/geo-mercator-eu.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2023/geo-mercator-eu_hu_6f78383885bc90be.png"
         alt="Europe" loading="lazy"/>
    </a><figcaption>
            <h4>Europe</h4><p>After seeing the map from North America it&rsquo;s rather surprising to see how dense European internet exchanges are. There&rsquo;s almost too much to comment on here but I will say that it&rsquo;s interesting how important Amsterdam, Frankfurt, and London are.</p>
        </figcaption>
</figure>

<figure><a href="https://kmcd.dev/posts/internet-map-2023/geo-mercator-apac.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2023/geo-mercator-apac_hu_7788de6116520159.png"
         alt="Asia-Pacific" loading="lazy"/>
    </a><figcaption>
            <h4>Asia-Pacific</h4><p>The area around Asia looks rather crazy, but it&rsquo;s important to note that fiber links can visit islands along the way in order to give them internet access. Those islands will have networking gear that &lsquo;drops&rsquo; and &lsquo;adds&rsquo; certain channels of the signal. The network device is typically a ROADM (reconfigurable optical add-drop multiplexer). I bet you can guess why they made that an acronym. Also, I think open peering in China is not really a thing as I believe most traffic goes through so-called backbone providers instead of through internet exchanges. This is likely related to the <a href="https://en.wikipedia.org/wiki/Internet_censorship_in_China" rel="external">great firewall of China</a>. On the map, you only see a good number of internet exchanges in Hong Kong.</p>
        </figcaption>
</figure>

<figure><a href="https://kmcd.dev/posts/internet-map-2023/geo-mercator-af.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2023/geo-mercator-af_hu_3e15f9cb6cfe073e.png"
         alt="Africa" loading="lazy"/>
    </a><figcaption>
            <h4>Africa</h4><p>Africa has many optical cables that travel along coasts. I suspect the terrestrial optical networks aren&rsquo;t as well developed in most parts of Africa. There are exceptions though. There is a lot of bandwidth capacity in internet exchanges in Capetown and Pretoria. Also, it&rsquo;s crazy to see how many optical cables lie at the bottom of the Suez canal.</p>
        </figcaption>
</figure>

<figure><a href="https://kmcd.dev/posts/internet-map-2023/geo-mercator-sa.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2023/geo-mercator-sa_hu_eec2bf84a1aadeaa.png"
         alt="South America" loading="lazy"/>
    </a><figcaption>
            <h4>South America</h4><p>In South America we have Fortaleza and Sao Paula have a lot of landings and internet exchanges.</p>
        </figcaption>
</figure>

<figure><a href="https://kmcd.dev/posts/internet-map-2023/svalbard.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2023/svalbard_hu_ddcaf8c15f10b468.png"
         alt="Svalbard" loading="lazy"/>
    </a><figcaption>
            <h4>Svalbard</h4><p>Svalbard is a set of islands owned by Norway that is waaay far north. It&rsquo;s one of those places that gets 6 months of day followed by 6 months of night. And it&rsquo;s always super cold. It&rsquo;s interesting to see two undersea cables to a place like this. Svalbard is where <a href="https://www.croptrust.org/work/svalbard-global-seed-vault/" rel="external">the global seed vault</a> lives, with seeds from all over the world. If you want to use these two cables you can check out <a href="https://www.spitsbergen-svalbard.com/photos-panoramas-videos-and-webcams/spitsbergen-webcams.html" rel="external">some webcams that are set up in Svalbard</a>.</p>
        </figcaption>
</figure>

<figure><a href="https://kmcd.dev/posts/internet-map-2023/maldives.png" class="spotlight" data-download="true">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2023/maldives_hu_3f697ba2805bc6f3.png"
         alt="Maldives" loading="lazy"/>
    </a><figcaption>
            <h4>Maldives</h4><p>Maldives is another interesting place where fiber optic cables live. You might notice that the fiber optic cables cross each other. It appears that it was done in order to connect islands that the first cable didn&rsquo;t service before. The cables were commissioned around 5 years apart. It must have been a hard 5 years if you lived on one of the islands that didn&rsquo;t get high-speed internet on the first round.</p>
        </figcaption>
</figure>

<h2 id="how-its-made">How it&rsquo;s made</h2>
<p>First, this is where I got the data from:</p>
<ul>
<li><a href="https://www.peeringdb.com" rel="external">PeeringDB</a> - PeeringDB is a user-driven database that offers information about network interconnection facilities and peering arrangements, supporting network administrators in optimizing Internet connectivity.</li>
<li><a href="https://simplemaps.com/data/world-cities" rel="external">Simple Maps</a> - I used this dataset to geolocate all internet exchanges in PeeringDB using the city and country fields.</li>
<li><a href="https://www.submarinecablemap.com/" rel="external">Submarine Cable Map</a> - TeleGeography maintains a database of all major submarine fiber optic cables and their status.</li>
</ul>
<p>Next, these are the different tools/languages that I used:</p>
<ul>
<li><a href="https://nodejs.org" rel="external">Javascript (nodejs)</a>
<ul>
<li><a href="https://d3js.org/" rel="external">D3</a> - D3.js is a powerful JavaScript library for data visualization that allows developers to create interactive and dynamic charts, graphs, and other visual representations on the web using HTML, SVG, and CSS.
<ul>
<li><a href="https://www.npmjs.com/package/d3-node" rel="external">d3-node</a> - a library that helps with running d3 inside of a nodejs environment</li>
<li><a href="https://www.npmjs.com/package/d3-geo" rel="external">d3-geo</a> - a library that handles translating coordinates for different map projections. I know it&rsquo;s controversial now, but the Earth isn&rsquo;t flat. So to make a flat image you have to pick how you are going to translate the globe coordinates onto a map. Despite its flaws, I used the <a href="https://en.wikipedia.org/wiki/Mercator_projection" rel="external">Mercator projection</a> because it is by far the most popular map projection.</li>
</ul>
</li>
</ul>
</li>
<li><a href="https://go.dev" rel="external">The Go Programming Language</a> - This is my current working language, so it&rsquo;s what I used to integrate with the PeeringDB API and do some data processing/validation.
<ul>
<li><a href="https://github.com/gmazoyer/peeringdb" rel="external">PeeringDB</a> - A library for talking to the PeeringDB API.</li>
</ul>
</li>
</ul>
<p>The pipeline looks like this:</p>
<div class="container">
  <pre class="mermaid">graph LR
    submarinecables[Submarine Cable Database] --> golang[Go Script]
    peeringdb[PeeringDB] --> golang[Go Script]
    geocities[Geolocation Database] --> golang[Go Script]
    golang[Go Script] --> node[Node JS Script]
    node[Node JS Script] --> svg[SVG]
    svg[SVG] --> convert[ImageMagick Convert] --> png[PNG]
    svg[SVG] --> morgify[ImageMagick Morgify] --> jpg[Displate JPG]
    style svg stroke:#f66,stroke-width:2px,stroke-dasharray: 5, 5;
    style jpg stroke:#f66,stroke-width:2px,stroke-dasharray: 5, 5;
    style png stroke:#f66,stroke-width:2px,stroke-dasharray: 5, 5;
  </pre>
</div>
<h3 id="mapping-of-cities-to-latlong-gps-coordinates">Mapping of Cities to lat/long GPS coordinates</h3>
<p>I had so many data validation issues when doing this project. I should have expected this more since PeeringDB is user-managed and there&rsquo;s no validation of the City field.</p>
<p>I had a lot of random issues mapping internet exchanges with their GPS coordinates. First, the geolocation database I was using is not complete. It only has around 43 thousand cities but there are many, many more towns and cities. The way I handled that was to manually map to the closest city that I do have geo coordinates. Additionally, the city field would be misspelled, formatted in a way differently than my database or the name of the city may have changed. This happens a lot because there are usually multiple ways to convert many languages into Latin characters. And sometimes cities just change their names. All-in-all I have 95 internet exchanges that I have had to manually map. I can&rsquo;t guarantee that they are all correct&hellip; but it is good enough. You can see all of the places that I manually mapped <a href="https://github.com/sudorandom/submarine-cable-map/blob/main/cmd/load-peering-data/cities.go" rel="external">on github</a>.</p>
<p>Here are my favorites:</p>
<ul>
<li>There were a few exchanges that spelled their city and sometimes the name of the exchange incorrectly. I&rsquo;ve sent a few emails to make them aware and a few have fixed it!</li>
<li>There is an exchange named &ldquo;Example IX&rdquo; which is obviously just used as an example: <a href="https://www.peeringdb.com/ix/4095" rel="external">entry on PeeringDB</a></li>
<li>The accepted Western name for the capital city of Ukraine changed from Kiev to Kyiv due to the efforts of a campaign called <a href="https://en.wikipedia.org/wiki/KyivNotKiev" rel="external">KyivNotKiev</a>. There are several internet exchanges still using the old spelling in its address.</li>
</ul>
<h2 id="thanks-for-reading">Thanks for reading</h2>
<p>So far I&rsquo;m pretty happy with the results of this little project. If I were to work more on this I would want there to be an actual heatmap instead of just drawing dots. I have attempted to do this but it was taking too long to figure out how to properly do it specifically with the node-d3 library. Also, there&rsquo;s probably a lot of data validation that I could do&hellip; but I do feel like it&rsquo;s not my job to fix this database so I&rsquo;m probably not going to do that for the sake of a small side project.</p>
<p>The part that is missing still is the terrestrial fiber links. There&rsquo;s not good public data on those for several reasons, but you can be assured that there are significant backbone fiber optic cables buried nearby almost every major highway and rail line in the US. So imagine lines that nearly mimic the US road system and you&rsquo;ll have an idea of what that map would look like. That may be the next step for this map since it&rsquo;s a bit hard to understand that we only have undersea cables.</p>
<p>References:</p>
<ul>
<li>Github: <a href="https://github.com/sudorandom/submarine-cable-map" rel="external">https://github.com/sudorandom/submarine-cable-map</a></li>
<li>PeeringDB: <a href="https://www.peeringdb.com/" rel="external">https://www.peeringdb.com/</a></li>
<li>Simple Maps (Geolocation Database): <a href="https://simplemaps.com/data/world-cities" rel="external">https://simplemaps.com/data/world-cities</a></li>
<li>Submarine Cable Map: <a href="https://www.submarinecablemap.com" rel="external">https://www.submarinecablemap.com</a></li>
</ul>
]]></content:encoded></item><item><title>softlayer-python: language bindings/CLI for a cloud company</title><link>https://kmcd.dev/posts/softlayer-python/</link><pubDate>Mon, 31 Jul 2023 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/softlayer-python/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/softlayer-python/cover_hu_6e49055ded8250.webp" /> &lt;/p>
                
                I wrote and maintained language bindings for a large cloud company. Join me as I reflect on that experience.
                </description><content:encoded><![CDATA[<p>I used to work for a public cloud company called <a href="https://en.wikipedia.org/wiki/IBM_Cloud#SoftLayer" rel="external">SoftLayer</a>. As a cloud company, there is an API that customers can use to provision new virtual servers, load balancers, firewalls and whatever else you might want. On our team, we used SoftLayer services as a customer might and we ended up proving new products and just&hellip; experiencing what it was like as a customer. I loved the concept. Our team heavily used this practice of so-called &ldquo;eating your own dog food.&rdquo;</p>
<p>When dogfooding our services it became painfully obvious that our API was extremely hard to use. When making tooling we had to go through extremely complex ordering APIs that were designed for internal use and were exposed publicly for convenience. I preferred to do this work in Python at the time, so I used our public API bindings. This was the <a href="https://github.com/softlayer/softlayer-python/tree/59b331dd9c33d9582d425be192fd3c2d63368d5d" rel="external">current state of the project on github</a>. Let me point out some of the issues I had:</p>
<ul>
<li>Didn&rsquo;t work with Python 3.</li>
<li>Used class variables incorrectly, making it impossible to use multiple instances of the client at the same time.</li>
<li>Had an awkward use of dictionary accessors that made things more confusing as a user.</li>
<li>Had absolutely zero unit tests.</li>
<li>The Python module name used upper-case characters.</li>
</ul>
<p>So&hellip; Immediately, I wanted to improve things. However, this was the first open-source project that I would modify that had a non-trivial number of users. Because of this, I learned to make strategic changes that followed the pattern of:</p>
<ul>
<li>Add several unit tests for the part of the code that</li>
<li>Add a new interface that</li>
<li>Use the new interface in the implementation of the old interface (to reduce code duplication)</li>
<li>Add deprecation warnings for the old interface.</li>
<li>After enough time, bump the major version of the library and remove all code that is deprecated.</li>
</ul>
<p>This pattern can be incredibly tedious. However, if you are maintaining a project used by many then you need to worry about upgrading. You need to write release notes. You need to give enough examples. You need to provide an upgrade path when you want to make breaking changes. This is super boring work, but it&rsquo;s the difference between &ldquo;when I upgrade this package everything breaks&rdquo; and &ldquo;when I upgrade this package, I get more cool new stuff&rdquo;.</p>
<p>For perspective, <a href="https://github.com/softlayer/softlayer-python" rel="external">here&rsquo;s what the code looks like now</a>. Note that there are now almost 2,000 unit tests. Note that the tests are run with several different versions of Python. And you should also note that there&rsquo;s an entire command line client in the repo as well!</p>
<p>The CLI was also created from the same motivations. For us, it makes automating and testing things much easier. Instead of clicking through a virtual machine creation web form for 20 minutes I could just copy/paste a command that I&rsquo;ve run before that could specify everything I would have to type or select anyway. In fact, here&rsquo;s an example of that!</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ slcli vs create --hostname<span style="color:#81a1c1">=</span>example --domain<span style="color:#81a1c1">=</span>softlayer.com -f B1_1X2X25 -o DEBIAN_LATEST_64  --datacenter<span style="color:#81a1c1">=</span>ams01 --billing<span style="color:#81a1c1">=</span>hourly
</span></span><span style="display:flex;"><span>This action will incur charges on your account. Continue? <span style="color:#81a1c1">[</span>y/N<span style="color:#81a1c1">]</span>: y
</span></span><span style="display:flex;"><span>    :..........:.................................:......................................:...........................:
</span></span><span style="display:flex;"><span>    :    ID    :               FQDN              :                 guid                 :         Order Date        :
</span></span><span style="display:flex;"><span>    :..........:.................................:......................................:...........................:
</span></span><span style="display:flex;"><span>    : <span style="color:#b48ead">70112999</span> : testtesttest.test.com : 1abc7afb-9618-4835-89c9-586f3711d8ea : 2019-01-30T17:16:58-06:00 :
</span></span><span style="display:flex;"><span>    :..........:.................................:......................................:...........................:
</span></span><span style="display:flex;"><span>    :.........................................................................:
</span></span><span style="display:flex;"><span>    :                            OrderId: <span style="color:#b48ead">12345678</span>                            :
</span></span><span style="display:flex;"><span>    :.......:.................................................................:
</span></span><span style="display:flex;"><span>    :  Cost : Description                                                     :
</span></span><span style="display:flex;"><span>    :.......:.................................................................:
</span></span><span style="display:flex;"><span>    :   0.0 : Debian GNU/Linux 9.x Stretch/Stable - Minimal Install <span style="color:#81a1c1">(</span><span style="color:#b48ead">64</span> bit<span style="color:#81a1c1">)</span>  :
</span></span><span style="display:flex;"><span>    :   0.0 : <span style="color:#b48ead">25</span> GB <span style="color:#81a1c1">(</span>SAN<span style="color:#81a1c1">)</span>                                                     :
</span></span><span style="display:flex;"><span>    :   0.0 : Reboot / Remote Console                                         :
</span></span><span style="display:flex;"><span>    :   0.0 : <span style="color:#b48ead">100</span> Mbps Public <span style="color:#eceff4">&amp;</span> Private Network Uplinks                       :
</span></span><span style="display:flex;"><span>    :   0.0 : <span style="color:#b48ead">0</span> GB Bandwidth Allotment                                        :
</span></span><span style="display:flex;"><span>    :   0.0 : <span style="color:#b48ead">1</span> IP Address                                                    :
</span></span><span style="display:flex;"><span>    :   0.0 : Host Ping and TCP Service Monitoring                            :
</span></span><span style="display:flex;"><span>    :   0.0 : Email and Ticket                                                :
</span></span><span style="display:flex;"><span>    :   0.0 : Automated Reboot from Monitoring                                :
</span></span><span style="display:flex;"><span>    :   0.0 : Unlimited SSL VPN Users <span style="color:#eceff4">&amp;</span> <span style="color:#b48ead">1</span> PPTP VPN User per account           :
</span></span><span style="display:flex;"><span>    :   0.0 : <span style="color:#b48ead">2</span> GB                                                            :
</span></span><span style="display:flex;"><span>    :   0.0 : <span style="color:#b48ead">1</span> x 2.0 GHz or higher Core                                      :
</span></span><span style="display:flex;"><span>    : 0.000 : Total hourly cost                                               :
</span></span><span style="display:flex;"><span>    :.......:.................................................................:
</span></span></code></pre></div><p>Here we&rsquo;re creating a VM inside of the Amsterdam data center that uses the latest Debian image with 2GB of RAM, a single CPU core and 25GB of disk space. All by copying a single command. This is super powerful. If you&rsquo;re wondering why everything is free it&rsquo;s because our account had special billing. 😛</p>
<p>After you create a VM, you can also list the running instances to see it:</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ slcli vs list
</span></span><span style="display:flex;"><span>:.........:............:....................:.......:........:................:..............:....................:
</span></span><span style="display:flex;"><span>:    id   : datacenter :       host         : cores : memory :   primary_ip   :  backend_ip  : active_transaction :
</span></span><span style="display:flex;"><span>:.........:............:....................:.......:........:................:..............:....................:
</span></span><span style="display:flex;"><span>: <span style="color:#b48ead">1234567</span> :   sjc01    :  test.example.com  :   <span style="color:#b48ead">4</span>   :   4G   :    12.34.56    :   65.43.21   :         -          :
</span></span><span style="display:flex;"><span>:.........:............:....................:.......:........:................:..............:....................:
</span></span></code></pre></div><p>The story is the same for several other products. Here&rsquo;s a list of what products are supported today:</p>
<ul>
<li>Account Management</li>
<li>Block Storage</li>
<li>Bandwidth Pools</li>
<li>CDN</li>
<li>Dedicated Hosts</li>
<li>DNS</li>
<li>Email</li>
<li>File Storage</li>
<li>Firewall</li>
<li>Global IP</li>
<li>Dedicated Hardware</li>
<li>Disk Images</li>
<li>IPSec</li>
<li>Licenses</li>
<li>Load Balancers</li>
<li>NAS</li>
<li>Object Storage</li>
<li>Ordering/Quotes</li>
<li>SSH Keys and Certificates</li>
<li>Security Groups</li>
<li>Subnets</li>
<li>Support Tickets</li>
<li>Users</li>
<li>VLANs</li>
<li>Virtual Servers</li>
</ul>
<p>It&rsquo;s remarkable how far this project has come. This started as a way to create some VMs with a script but it became an extremely important interface for the entire suite of cloud products. Slowly, the CLI grew to what it is today. This wasn&rsquo;t a result of just me. Instead, several members of mine and other people&rsquo;s teams contributed to the project. There were periods when I didn&rsquo;t write a lot of code but would spend most of my time reviewing and guiding others to make their contributions. I learned a lot about the importance of enforcing a single style and keeping the quality of code high. Essentially, the ways customers interfaced was through the website, API or the CLI.</p>
<p>The CLI also drove more usage of the Python client. It encouraged this growth in several ways:</p>
<ul>
<li>It showcased what was possible with the API in a way that the documentation just can&rsquo;t do.</li>
<li>It acted as a good reference for &ldquo;how can I do this with the API&rdquo;. The more we added to the CLI, the fewer extra examples we needed to make. It is very important that the CLI was also open source for this reason.</li>
<li>It had a verbose flag that showed the API calls that were being made, greatly increasing visibility into how it works.</li>
</ul>
<p>Here&rsquo;s an example of what a command looks like when running a command using the verbose flag.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ slcli -v vs detail <span style="color:#b48ead">74397127</span>
</span></span><span style="display:flex;"><span>Calling: SoftLayer_Virtual_Guest::getObject<span style="color:#81a1c1">(</span>id<span style="color:#81a1c1">=</span>74397127, mask<span style="color:#81a1c1">=</span><span style="color:#a3be8c">&#39;id,globalIdentifier,fullyQualifiedDomainName,hostname,domain&#39;</span>, filter<span style="color:#81a1c1">=</span><span style="color:#a3be8c">&#39;None&#39;</span>, args<span style="color:#81a1c1">=()</span>, limit<span style="color:#81a1c1">=</span>None, offset<span style="color:#81a1c1">=</span>None<span style="color:#81a1c1">))</span>
</span></span><span style="display:flex;"><span>Calling: SoftLayer_Virtual_Guest::getReverseDomainRecords<span style="color:#81a1c1">(</span>id<span style="color:#81a1c1">=</span>77460683, mask<span style="color:#81a1c1">=</span><span style="color:#a3be8c">&#39;&#39;</span>, filter<span style="color:#81a1c1">=</span><span style="color:#a3be8c">&#39;None&#39;</span>, args<span style="color:#81a1c1">=()</span>, limit<span style="color:#81a1c1">=</span>None, offset<span style="color:#81a1c1">=</span>None<span style="color:#81a1c1">))</span>
</span></span><span style="display:flex;"><span>:..................:..............................................................:
</span></span><span style="display:flex;"><span>:       name       :                            value                             :
</span></span><span style="display:flex;"><span>:..................:..............................................................:
</span></span><span style="display:flex;"><span>:  execution_time  :                          2.020334s                           :
</span></span><span style="display:flex;"><span>:    api_calls     :        SoftLayer_Virtual_Guest::getObject <span style="color:#81a1c1">(</span>1.515583s<span style="color:#81a1c1">)</span>        :
</span></span><span style="display:flex;"><span>:                  : SoftLayer_Virtual_Guest::getReverseDomainRecords <span style="color:#81a1c1">(</span>0.494480s<span style="color:#81a1c1">)</span> :
</span></span><span style="display:flex;"><span>:     version      :                   softlayer-python/v5.7.2                    :
</span></span><span style="display:flex;"><span>:  python_version  :           3.7.3 <span style="color:#81a1c1">(</span>default, Mar <span style="color:#b48ead">27</span> 2019, 09:23:15<span style="color:#81a1c1">)</span>             :
</span></span><span style="display:flex;"><span>:                  :              <span style="color:#81a1c1">[</span>Clang 10.0.1 <span style="color:#81a1c1">(</span>clang-1001.0.46.3<span style="color:#81a1c1">)]</span>              :
</span></span><span style="display:flex;"><span>: library_location : /Users/chris/Code/py3/lib/python3.7/site-packages/SoftLayer  :
</span></span><span style="display:flex;"><span>:..................:..............................................................:
</span></span></code></pre></div><p>The more <code>v</code> characters you add, the more verbose the output gets. If you use <code>-vvv</code> then you will get the equivalent cURL commands to make the same API calls, which should be clear enough for any developer to make a client against the API.</p>
<div class="highlight"><pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ slcli -vvv account summary
</span></span><span style="display:flex;"><span>curl -u $SL_USER:$SL_APIKEY -X GET -H <span style="color:#a3be8c">&#34;Accept: */*&#34;</span> -H <span style="color:#a3be8c">&#34;Accept-Encoding: gzip, deflate, compress&#34;</span>  <span style="color:#a3be8c">&#39;https://api.softlayer.com/rest/v3.1/SoftLayer_Account/getObject.json?objectMask=mask%5B%0A++++++++++++nextInvoiceTotalAmount%2C%0A++++++++++++pendingInvoice%5BinvoiceTotalAmount%5D%2C%0A++++++++++++blockDeviceTemplateGroupCount%2C%0A++++++++++++dedicatedHostCount%2C%0A++++++++++++domainCount%2C%0A++++++++++++hardwareCount%2C%0A++++++++++++networkStorageCount%2C%0A++++++++++++openTicketCount%2C%0A++++++++++++networkVlanCount%2C%0A++++++++++++subnetCount%2C%0A++++++++++++userCount%2C%0A++++++++++++virtualGuestCount%0A++++++++++++%5D&#39;</span>
</span></span></code></pre></div><p>In summary, this was an incredibly successful side project. What started as a small script for internal use turned into a Swiss army knife that was a completely new way to access all products that SoftLayer offered. I learned so much about maintaining an open-source project, choosing reliable libraries to build on, code quality/style, and so much more.</p>
<p>References:</p>
<ul>
<li>Github: <a href="https://github.com/softlayer/softlayer-python" rel="external">https://github.com/softlayer/softlayer-python</a></li>
<li>Documentation: <a href="https://softlayer-python.readthedocs.io/en/latest/" rel="external">https://softlayer-python.readthedocs.io/en/latest/</a></li>
</ul>
]]></content:encoded></item><item><title>SwFTP: SFTP/FTP Server For Openstack Swift</title><link>https://kmcd.dev/posts/swftp/</link><pubDate>Sun, 30 Jul 2023 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/swftp/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/swftp/cover_hu_f456d62d712f6990.webp" /> &lt;/p>
                
                Describing an old project of mine from 2014; an SFTP/FTP interface over an object storage API using Python Twisted.
                </description><content:encoded><![CDATA[<p>I used to work for a public cloud company called <a href="https://en.wikipedia.org/wiki/IBM_Cloud#SoftLayer" rel="external">SoftLayer</a>. Before <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/" rel="external">S3’s API</a> was the de-facto API standard that object storage services used several object storage APIs seemed like they could claim that crown. The company I worked for, SoftLayer, had recently come out with an Object Storage service based on the <a href="https://wiki.openstack.org/wiki/Swift" rel="external">OpenStack Swift</a> project. It came with its API, which is great. However, at the time, it was hard to get buy-in from customers to use under-supported APIs. They’d have to use unfamiliar tooling or even develop tooling themselves just to transfer files around&hellip; And if they currently had a product that didn’t support OpenStack Swift they may just be stuck. So I was charged with coming up with a solution for these customers.</p>
<p>After testing out a few different ideas I ended up deciding on the SFTP protocol. Object Storage doesn’t translate 1:1 with a “normal” filesystem but it was close enough. This might seem a strange decision in modern days where everyone has S3 integration but this was done as a way to integrate with older products and give customers a much more familiar feel with interacting with their data. I decided to call the project <a href="https://github.com/softlayer/swftp" rel="external">SwFTP</a>.</p>
<div class="container">
  <pre class="mermaid">graph LR
    cyberduck((Cyberduck)) -- SFTP --> swftp((SwFTP))
    filezilla((FileZilla)) -- SFTP --> swftp((SwFTP))
    client((SFTP/FTP Client)) -- SFTP or FTP --> swftp((SwFTP))
    swftp((SwFTP)) -- HTTPS --> swift((Swift))
  </pre>
</div>
<p>I was all set. I needed to just&hellip; implement an SFTP server. Oof. That’s actually very challenging. You see, the <a href="https://www.ietf.org/rfc/rfc0913.txt" rel="external">SFTP protocol</a> has lived for over a decade at that point so it has gone through several RFC drafts and it supports various extensions&hellip; And it’s built on top of SSH, which is also fairly complex to implement. So I needed to use something more proven. I needed a library that I could plug in OpenStack Swift integration into without needing to implement the wire protocol itself. Again, at the time this kind of ability was fairly rare. I was very experienced with Python and a bit with Go (nowadays I would totally write this project in Go, using this <a href="https://pkg.go.dev/github.com/pkg/sftp" rel="external">SFTP library</a> and implementing the <code>fs.Walker</code> interface). That would have made my life so much simpler. But no, I was essentially stuck with Python’s <a href="https://twisted.org/" rel="external">Twisted</a>. Twisted has a library called <a href="https://docs.twisted.org/en/stable/api/twisted.conch.html" rel="external">&ldquo;Conche&rdquo;</a> which implements the SSH protocol and it allows you to hook into the SFTP subsystem, which is exactly what I needed. Twisted seemed to be the best option at the time, but it was (and still is) very hard to work with. The failure modes can be very complex. Plus, the SFTP protocol is fairly complex and SFTP clients will behave in vastly different ways. For instance, some clients, in order to maximize throughput, will concurrently send several batches of data at once when uploading a file without waiting for the acknowledgment to be received. We can’t behave similarly with an object storage API call so I needed to force the concurrent write requests to queue up properly until it was their turn to be sent down the wire to Swift. This, alone, is complex but I was also using an unfamiliar framework that has completely different sync primitives than I’ve used in the past&hellip; So I found this work to be very challenging&hellip; but in the end, it was very rewarding.</p>
<p>SwFTP was never the only project I was working on so my attention was split multiple ways. Despite that, after a year of working on this project, it was finally good enough to deploy to production as a supported service. Testing took a long time because, as I said, many SFTP clients behave very differently. I am happy about where I got the functional test framework since I was able to easily write code that would reproduce errors that we saw when testing, including some super complicated cases of race conditions. From this experience, I&rsquo;ve learned some very important lessons about functional and manual testing.</p>
<p>All-in-all, SwFTP ended up being a success and a lot of data was transferred using this service. Thanks to my manager and support from others in the company I was able to perform all of this iteration and development as an open source project. There were no SoftLayer-specific implementation details included here so others could (and did) deploy the project for their own OpenStack Swift clusters.</p>
<blockquote>
<p>By the way, if you’re having issues pronouncing “SwFTP” in your head then you aren’t alone. I used to call it something like “Swefteepee”.</p>
</blockquote>
<p>References:</p>
<ul>
<li>SwFTP Github - <a href="https://github.com/softlayer/swftp" rel="external">https://github.com/softlayer/swftp</a></li>
<li>Conch (SSH library) - <a href="https://docs.twisted.org/en/stable/api/twisted.conch.html" rel="external">https://docs.twisted.org/en/stable/api/twisted.conch.html</a></li>
<li>Writing a client using Conch - <a href="https://docs.twisted.org/en/twisted-18.9.0/conch/howto/conch_client.html" rel="external">https://docs.twisted.org/en/twisted-18.9.0/conch/howto/conch_client.html</a></li>
</ul>
]]></content:encoded></item><item><title>Video: Morning Copenhagen Commute</title><link>https://kmcd.dev/posts/morning-copenhagen-commute/</link><pubDate>Sun, 30 Jul 2023 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/morning-copenhagen-commute/</guid><description> 
                
                A video showing my commute to work on a bike from the island of Amager to the heart of Copenhagen, Denmark.
                </description><content:encoded><![CDATA[<p><div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/gqjiylaCRuY?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<a href="https://www.youtube.com/watch?v=gqjiylaCRuY" rel="external">Youtube Link (if embed doesn&rsquo;t work)</a></p>
<p>One year ago I made the leap and moved from Dallas, Texas to Copenhagen, Denmark with my family. There were many reasons for this move but one of the biggest motivators was to be able to live in a city where cycling was treated as a first-class mode of transportation. Cycling is efficient, great for your health, great for your mental health and is just a more pleasant way to get to work.</p>
<p>Last year I went from never riding a bike (seriously, I had only been on a bike 3 times, ever) to cycling being an integral part of my life. I am so thankful to live in such a bike-friendly city of Copenhagen and I’m lucky to have my new job in the city center. Linked above is an admittedly boring video of my commute to work but being able to bike to work is such an accomplishment for me that I figured I’d share it here.</p>
]]></content:encoded></item><item><title>Goodbye Evepraisal</title><link>https://kmcd.dev/posts/goodbye-evepraisal/</link><pubDate>Mon, 24 Jul 2023 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/goodbye-evepraisal/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/goodbye-evepraisal/cover_hu_f33c30b6d1654b6e.webp" /> &lt;/p>
                
                Saying goodbye to an amazing long-running project; evepraisal price checking utility for the MMORPG game Eve Online
                </description><content:encoded><![CDATA[<blockquote>
<p>I posed a <a href="https://kmcd.dev/posts/lessons-from-a-decades-long-project/">new article about operating Evepraisal for over a decade</a>. It covers the technical details and lessons that I learned throughout the years.</p>
</blockquote>
<p><a href="https://kmcd.dev/posts/evepraisal.com/">Evepraisal</a> has been a really fun project for me. I think it has made a real impact on how Eve Online is played by many players. However, the time has come to shut it down.</p>
<p>First off, if you are looking for alternatives, you may want to try one of these websites:</p>
<ul>
<li><a href="https://evetycoon.com/appraisal" rel="external">Eve Tycoon</a></li>
<li><a href="https://market.fuzzwork.co.uk/appraisal/" rel="external">Fuzzwork</a></li>
<li><a href="https://janice.e-351.com/" rel="external">Janice</a></li>
<li><a href="https://www.eveworkbench.com/tools/appraisal" rel="external">EVE Workbench</a></li>
</ul>
<p>Recently, CCP banned the IP address that I currently use to gather market data from Eve Online&rsquo;s ESI API. I started to work towards mitigating the issue by contacting support, but I stopped to think for a second about just&hellip; not. This may not surprise most people based on how nothing changed with the website for years, but I haven&rsquo;t played Eve Online for several years now. As such, I&rsquo;ve been mostly doing extremely rare maintenance on the tool. I have enough monitoring that usually I am alerted when something breaks (that&rsquo;s how I even know that CCP banned my IP from ESI) and I spend some time fixing it and then I move on with my life. However, I&rsquo;ve become more and more out of touch with the game. It appears to be changing and it just feels weird running a tool for a game I don&rsquo;t have installed anywhere&hellip; for years&hellip;</p>
<p><strong>So I&rsquo;ve decided to just pull the plug.</strong> I&rsquo;m sorry that advertisements may have been annoying but it essentially only made enough money to keep the site running. Thank you to everyone who has supported this project over the years. Eve Online has been a great game for many people for many years. I am happy to have had my role, however small, in shaping the game.</p>
<p>The source code for Evepraisal is open to all to use and modify. You can find it at <a href="https://github.com/evepraisal/go-evepraisal" rel="external">github.com/evepraisal/go-evepraisal</a>. Also, see my <a href="https://kmcd.dev/posts/evepraisal.com/">other post about Evepraisal</a> for more information about how Evepraisal was made and what made it special.</p>
<p>o7, fly safe</p>
<blockquote>
<p>If you have an interest in programming with Go, the Internet, gRPC or just want to read words that I write, check out <a href="https://kmcd.dev/">my blog</a>. You can start by reading the other <a href="https://kmcd.dev/tags/evepraisal/">evepraisal-related posts</a>.</p>
</blockquote>
]]></content:encoded></item><item><title>Visualizing the spectrum of the sun (Part 2)</title><link>https://kmcd.dev/posts/sun-spectra-image-v2.svg/</link><pubDate>Mon, 12 Sep 2022 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/sun-spectra-image-v2.svg/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/sun-spectra-image-v2.svg/thumbnail_hu_e9954082d0ce26c3.webp" /> &lt;/p>
                
                Revisiting visualizing the light that comes from the sun. Spectrum analysis, wavelengths of light, data visualization
                </description><content:encoded><![CDATA[<h2 id="basic-details">Basic Details</h2>
<p>You may know about my <a href="https://kmcd.dev/posts/sun-spectra-image.svg/">previous sun spectra project</a> where I created a visualization of the sun&rsquo;s observed electromagnetic spectrum. Well, this is the same thing but I made some different decisions on how to visualize it this time and I think it turned out beautifully. I thought it looked so great that you can get it on <a href="https://displate.com/displate/5622874" rel="external">Displate</a>!</p>
<p><a href="https://github.com/sudorandom/sun-fingerprint" rel="external">Github</a> | <a href="https://github.com/sudorandom/sun-fingerprint/tree/main/output" rel="external">All output images</a> | <a href="https://displate.com/displate/5622874" rel="external">Displate</a></p>
<hr>
<h2 id="the-suns-spectra-visible-light">The sun&rsquo;s spectra (visible light)</h2>
<p>This represents the visible light that the sun puts out. Notice the holes. These holes and dim spots tell us what the sun is made of, how it works and even the temperature of different parts of the sun.</p>
<p>Notice that you can zoom in an incredible amount. This is possible because these images are SVG files that use vectors to represent the image instead of a pixel map. This allows the images to essentially be infinitely zoomable.
<img src="https://kmcd.dev/posts/sun-spectra-image-v2.svg/visible.svg" alt="Spectra of the sun in the visible spectrum" title="The Sun">
<a href="https://kmcd.dev/posts/sun-spectra-image-v2.svg/visible.svg">Click here to see the full resolution</a></p>
<h2 id="the-suns-spectra-full-spectrum">The sun&rsquo;s spectra (full spectrum)</h2>
<p>Here&rsquo;s the entire spectrum. As you may know, there is light that we cannot see because it either has too high or too low of a frequency. This visualization shows you the intensity levels for frequencies even outside of the visual spectrum. The pinkish color at the top is Ultraviolet light. The magenta-colored section under read is infrared light. Everything below that can be classified as radio waves. Scroll down a bit more to see an image with these sections annotated.</p>
<p><img src="https://kmcd.dev/posts/sun-spectra-image-v2.svg/non-visible.svg" alt="Spectra of the sun in all spectrums" title="The Sun">
<a href="https://kmcd.dev/posts/sun-spectra-image-v2.svg/non-visible.svg">Click here to see the full resolution</a></p>
<h2 id="the-suns-spectra-annotated">The sun&rsquo;s spectra (annotated)</h2>
<p>Here is a version of the image that has text which describes what you&rsquo;re seeing.</p>
<p><img src="https://kmcd.dev/posts/sun-spectra-image-v2.svg/annotated.svg" alt="Spectra of the sun, annotated" title="The Sun">
<a href="https://kmcd.dev/posts/sun-spectra-image-v2.svg/annotated.svg">Click here to see the full resolution</a></p>
<p><a href="https://en.wikipedia.org/wiki/Spectroscopy" rel="external">Spectroscopy</a> is a very interesting science and the basis for it is rooted in our understanding of quantum physics. We know a lot about how the sun and other stars work using this science. However, we don&rsquo;t fully understand EVERY observation in these spectral lines. There&rsquo;s always more to learn and understand. Physics in particular has several massive mysteries just waiting to be solved.</p>
<p>A better explanation can be found in <a href="https://www.youtube.com/watch?v=gVZwdYZqCUI" rel="external">this video</a>](<a href="https://www.youtube.com/watch?v=gVZwdYZqCUI" rel="external">https://www.youtube.com/watch?v=gVZwdYZqCUI</a>) from the youtube channel <a href="https://www.youtube.com/@besmart" rel="external">Be smart</a>.</p>
]]></content:encoded></item><item><title>Visualizing the Internet (2022)</title><link>https://kmcd.dev/posts/internet-map-2022/</link><pubDate>Sat, 26 Feb 2022 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/internet-map-2022/</guid><description> 
                
                I drew a pretty map that shows the underwater cables that carry our data around the world; fiber optic cables, submarine cables
                </description><content:encoded><![CDATA[<blockquote>
<p>There is an updated version of this map detailed <a href="https://kmcd.dev/posts/internet-map-2023/">in this updated post</a>.</p>
</blockquote>
<h3 id="basic-details">Basic Details</h3>
<p>I used data from the <a href="https://submarinecablemap.com" rel="external">submarinecablemap.com</a> website to create my visualization of Submarine Cables that live under our oceans and carry the majority of trans-continental internet traffic. Mostly, I wanted a &lsquo;dark mode&rsquo; version of the map but I also plan on adding some interesting annotations from different sources and computing some metrics&hellip; Like there is enough fiber optic cable under the oceans to wrap the earth over 103 times! These SVGs were made with javascript, <a href="https://d3js.org" rel="external">d3</a>. I also used this experience to look at different map projections, which is neat.</p>
<p><a href="https://github.com/sudorandom/submarine-cable-map" rel="external">Github</a> | <a href="https://github.com/sudorandom/tree/main/output" rel="external">All output images</a></p>
<hr>
<p>Here are the resulting images.</p>
<h3 id="geo-mercatorsvg">geo-mercator.svg</h3>
<p><figure><a href="https://kmcd.dev/posts/internet-map-2022/geo-mercator.svg"
            
             data-description="Cable map using the Mercator projection"class="spotlight"
            data-preload="true"
            data-download="true"
            data-progress="true"
            data-infinite="true"
            data-spinner="true"
            data-autofit="true"
            data-fit="cover">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2022/geo-mercator-small_hu_5ae8c8106c82c45d.png"
         alt="Map of the Internet"/>
    </a>
</figure>

<a href="https://kmcd.dev/posts/internet-map-2022/geo-mercator.svg">click here for full resolution</a></p>
<h3 id="geo-conic-conformalsvg">geo-conic-conformal.svg</h3>
<p><figure><a href="https://kmcd.dev/posts/internet-map-2022/geo-conic-conformal.svg"
            
             data-description="Cable map using the conic conformal projection"class="spotlight"
            data-preload="true"
            data-download="true"
            data-progress="true"
            data-infinite="true"
            data-spinner="true"
            data-autofit="true"
            data-fit="cover">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2022/geo-conic-conformal-small_hu_da3dfc6110e80843.png"
         alt="Map of the Internet"/>
    </a>
</figure>

<a href="https://kmcd.dev/posts/internet-map-2022/geo-conic-conformal.svg">click here for full resolution</a></p>
<h3 id="geo-conic-equal-areasvg">geo-conic-equal-area.svg</h3>
<p><figure><a href="https://kmcd.dev/posts/internet-map-2022/geo-conic-equal-area.svg"
            
             data-description="Cable map using the conic conformal projection"class="spotlight"
            data-preload="true"
            data-download="true"
            data-progress="true"
            data-infinite="true"
            data-spinner="true"
            data-autofit="true"
            data-fit="cover">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2022/geo-conic-equal-area-small_hu_eecb43789d949596.png"
         alt="Map of the Internet"/>
    </a>
</figure>

<a href="https://kmcd.dev/posts/internet-map-2022/geo-conic-equal-area.svg">click here for full resolution</a></p>
<h3 id="geo-natural-earth-1svg">geo-natural-earth-1.svg</h3>
<p><figure><a href="https://kmcd.dev/posts/internet-map-2022/geo-natural-earth-1.svg"
            
             data-description="Cable map using the conic conformal projection"class="spotlight"
            data-preload="true"
            data-download="true"
            data-progress="true"
            data-infinite="true"
            data-spinner="true"
            data-autofit="true"
            data-fit="cover">
    
    
    
    
        
            
            
                
            
            
        
    

    
    
    

    
    
        
    

    <img src="https://kmcd.dev/posts/internet-map-2022/geo-natural-earth-1-small_hu_1ae47e3536b88e66.png"
         alt="Map of the Internet"/>
    </a>
</figure>

<a href="https://kmcd.dev/posts/internet-map-2022/geo-natural-earth-1.svg">click here for full resolution</a></p>
]]></content:encoded></item><item><title>Evepraisal: A price estimation tool for Eve Online</title><link>https://kmcd.dev/posts/evepraisal.com/</link><pubDate>Sun, 20 Feb 2022 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/evepraisal.com/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/evepraisal.com/thumbnail_hu_7b3a5865607964e2.webp" /> &lt;/p>
                
                Evepraisal - Introducing the price estimation tool for the MMORPG game Eve Online that I created. Sometimes the best way to play is not to play
                </description><content:encoded><![CDATA[<h2 id="basic-details">Basic Details</h2>
<p>Evepraisal is a web-based bulk price estimation tool for the game Eve Online. It has gained wide use and has a fairly high amount of traffic for such a simple utility. Almost anyone who plays the game has used it.</p>
<p><a href="https://github.com/evepraisal/go-evepraisal" rel="external">Go version on GitHub</a> | <a href="https://github.com/evepraisal/python-evepraisal" rel="external">Python version on Github</a></p>
<hr>
<h2 id="how-it-was-created">How it was created</h2>
<p>This is a story about how I came to create <a href="https://evepraisal.com" rel="external">evepraisal.com</a>, a popular tool for Eve Online that is commonly used to price check cargo scans, contracts, EFT fittings, assets, and more. If none of that means anything to you that&rsquo;s totally fine! I barely know, too.</p>
<p>Eve Online has always been an interesting game for me. For the uninitiated, Eve Online is a space-themed single-sharded MMO game that boasts an incredibly realistic economy and allows players to take high-stakes risks. This is the game that has had several extremely large fights over player-controlled territories involving thousands of people. It’s a game where politics, espionage and propaganda are effective weapons against your enemy.</p>
<h2 id="burn-jita">Burn Jita</h2>
<p>2 years ago, during a large player-made event called “Burn Jita”, an in-game alliance named Goonswarm and various other null-sec entities sought to blockade the biggest market hub in the game in hopes that the established trading center of Eve Online would be significantly impacted. I happened to be in an alliance comprising “various other null-sec entities” so I participated in the event. How could I not? Here’s how the scheme worked:</p>
<ol>
<li>Designated people would cargo scan ships passing through nearby systems headed toward Jita. The information would be relayed through in-game and out-of-game channels.</li>
<li>A fleet of ships would be waiting to strike juicy targets foolish enough to enter or leave the trade hub with valuable cargo. The ships they were flying were cheap and could deal a lot of instant damage.</li>
<li>Once a transport ship with a significant amount of goods entered the system the fleet would strike. Jita is located in high sec which means every attacking ship would pretty quickly be destroyed by the computer-controlled police force, but the damage output of the entire fleet assured that the transport ship would turn into a pile of wreckage and loot.</li>
<li>Rinse; repeat.</li>
</ol>
<p>Now take a second to appreciate the fact that several of these fleets were operating at any given time and the ships used to do these attacks were provided, free of charge, by each participating alliance.</p>
<p>My role in the process above was being one of the people who did step 1. I did a fair amount of ganking with the fleet but what I really enjoyed was pointing to a person and then waiting 10 minutes to see that his ship turned into a mangled wreck and his goods (or what’s left of them) were stolen to aid the further destruction of people like him. It was my (and my counterpart’s) actions that started the chain of events leading to the death of an enterprising player who paid no attention to player events. The act of firing space bullets was a detail.</p>
<h2 id="necessity-is-the-mother-of-invention">Necessity is the mother of invention</h2>
<p>After a few hours of scanning the cargo of every ship in sight, I realized that many freighters were stuffing their cargo hold with lots of useless junk to hide the true value of the cargo that they were carrying. This was a problem. Deciding who to attack was a time-sensitive task and not having a reliable cost estimate of what the ship contained made things harder than they had to be.</p>
<p>That’s where something clicked in my mind and over the next 3 hours I made a web-based tool that completely solved the ambiguity of this entire process and changed ganking into a legitimate economically-driven career that boasts cost/benefit analysis similar to (yet more exciting than) most of the other more reputable careers in Eve Online. That’s a big claim, but I don’t think I am far off.</p>
<h2 id="how-it-works">How it works</h2>
<p>When you cargo-scan a potential target’s cargo hold, you get a list of all its contents. Up until recently, all you could do was look at the list and that was it. However, CCP recently added the ability to copy/paste from many different places in the Eve Online UI including the cargo scan result window. Cool, I thought. I could have users give me a list of all the items in the potential target’s hanger. Now I just needed a way to calculate the prices. Eve Online has an amazing developer community. There were (and are) existing websites that had daily database dumps of aggregate pricing data for every market item in the game. I downloaded one of the database dumps and used that data as my price reference. After that, it was a matter of making a small website where people could give it cargo scan results and it would give back a reasonably accurate guess of the cargo’s value in less than a second. These estimates could be much better than what a human could do manually at the same time.</p>
<p>I randomly linked the website that I had just created in one of the fleet channels and it was a short time later that someone in Goonswarm broadcasted the link to the Goonswarm jabber server. I instantly got a flood of interested people trying out the tool and, to my surprise, the site held up fine. Later, I added the ability to share results so that the many scanners wouldn’t have to communicate the estimate they’re seeing from my website which further streamlined the process. In hindsight, what seemed like a small feature that was fairly easy to implement turned out to be the biggest selling point. The immediate feedback I got from people was invaluable and shaped my thoughts on the problem. To this day I’ve never had a bad experience asking people (even those who didn’t like the tool) what could be better.</p>
<p>The Burn Jita event continued for a couple more days and instead of cargo scanning ships, I was working on improving the tool that others used to find targets to gank more efficiently. The rest is history.</p>
<p><img src="https://kmcd.dev/posts/evepraisal.com/screenshot_appraisal.png" alt="Screenshot of appraisal" title="Evepraisal"></p>
<p>The website slowly evolved into what is now known as Evepraisal. I added support for many different formats and switched from using price data from a static database dump to using more frequently updated sources like Eve-Central, Eve-marketdata.com and eventually the official ESI API had support for everything IU needed. I’ve heard from several people who are career gankers in Eve who leverage Evepraisal to be profitable.</p>
<p><img src="https://kmcd.dev/posts/evepraisal.com/screenshot_index.png" alt="Screenshot of evepraisal.com homepage" title="Evepraisal"></p>
<p>I&rsquo;ve since shutdown the website. <a href="https://kmcd.dev/posts/goodbye-evepraisal/">See more about the reasoning here</a>.</p>
]]></content:encoded></item><item><title>Visualizing the spectrum of the sun</title><link>https://kmcd.dev/posts/sun-spectra-image.svg/</link><pubDate>Sat, 19 Feb 2022 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/sun-spectra-image.svg/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/sun-spectra-image.svg/thumbnail_hu_7379da2aa18637b4.webp" /> &lt;/p>
                
                Visualizing the visual spectrum of the sun. Spectrum analysis, wavelengths of light, data visualization
                </description><content:encoded><![CDATA[<h2 id="basic-details">Basic Details</h2>
<p>We all know the light that we get from the sun is white, meaning it contains about the same amount of every single color. If it had more green than other colors then the sun would give off green light. That&rsquo;s awesome. Except&hellip; it&rsquo;s not really true. There are certain colors that the sun does NOT give us. There are essentially holes in every rainbow. And the images below show exactly where those holes are.</p>
<p><a href="https://github.com/sudorandom/sun-fingerprint" rel="external">Github</a> | <a href="https://github.com/sudorandom/sun-fingerprint/tree/main/output" rel="external">All output images</a></p>
<hr>
<h2 id="the-suns-spectra-visible-light">The sun&rsquo;s spectra (visible light)</h2>
<p><img src="https://kmcd.dev/posts/sun-spectra-image.svg/visible.svg" alt="Spectra of the sun in the visible spectrum" title="The Sun">
<a href="https://kmcd.dev/posts/sun-spectra-image.svg/visible.svg">Click here to see the full resolution</a></p>
<h2 id="the-suns-spectra-full-spectrum">The sun&rsquo;s spectra (full spectrum)</h2>
<p>I also made a version that shows the NON-visible spectrum. I can&rsquo;t use pretty colors for this because we literally don&rsquo;t have colors to map this part of the spectrum to. So I used greyscale (a gradient from black to white) to denote the intensity of this kind of light emitted from the sun. The image looks blurry but that&rsquo;s an artifact of how precise the data is.</p>
<p><img src="https://kmcd.dev/posts/sun-spectra-image.svg/non-visible.svg" alt="Spectra of the sun in all spectrums" title="The Sun">
<a href="https://kmcd.dev/posts/sun-spectra-image.svg/non-visible.svg">Click here to see the full resolution</a></p>
<h2 id="the-suns-spectra-annotated">The sun&rsquo;s spectra (annotated)</h2>
<p>I also made a version that has text that describes what you&rsquo;re seeing.</p>
<p><img src="https://kmcd.dev/posts/sun-spectra-image.svg/annotated.svg" alt="Spectra of the sun, annotated" title="The Sun">
<a href="https://kmcd.dev/posts/sun-spectra-image.svg/annotated.svg">Click here to see the full resolution</a></p>
]]></content:encoded></item></channel></rss>