<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>Webdev on kmcd.dev</title><link>https://kmcd.dev/tags/webdev/</link><description>Recent content in Webdev on kmcd.dev</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>All Rights Reserved</copyright><lastBuildDate>Wed, 04 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://kmcd.dev/tags/webdev/index.xml" rel="self" type="application/rss+xml"/><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"
             alt="" 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" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
</span></span><span class="line"><span class="cl"><span class="n">Content-Type</span><span class="o">:</span> <span class="l">text/plain</span>
</span></span><span class="line"><span class="cl"><span class="n">Transfer-Encoding</span><span class="o">:</span> <span class="l">chunked</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="m">8</span><span class="se">\r\n</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>kmcd.dev<span class="se">\r\n</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="m">12</span><span class="se">\r\n</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span>is<span class="w"> </span>awesome!<span class="se">\r\n</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="m">0</span><span class="se">\r\n</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="se">\r\n</span><span class="w">
</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl --http1.1 -I https://kmcd.dev
</span></span><span class="line"><span class="cl">HTTP/1.1 <span class="m">200</span> OK
</span></span><span class="line"><span class="cl">Date: Sat, <span class="m">24</span> Jan <span class="m">2026</span> 10:12:55 GMT
</span></span><span class="line"><span class="cl">Content-Type: text/html<span class="p">;</span> <span class="nv">charset</span><span class="o">=</span>utf-8
</span></span><span class="line"><span class="cl">Connection: keep-alive
</span></span><span class="line"><span class="cl">Cache-Control: max-age<span class="o">=</span>31536000, public
</span></span><span class="line"><span class="cl">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" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="nf">PUT</span> <span class="nn">/images</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
</span></span><span class="line"><span class="cl"><span class="n">Host</span><span class="o">:</span> <span class="l">images.example.com</span>
</span></span><span class="line"><span class="cl"><span class="n">Content-Type</span><span class="o">:</span> <span class="l">image/png</span>
</span></span><span class="line"><span class="cl"><span class="n">Content-Length</span><span class="o">:</span> <span class="l">500000</span>
</span></span><span class="line"><span class="cl"><span class="n">Expect</span><span class="o">:</span> <span class="l">100-continue</span>
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ListenAndServe starts the server.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="o">*</span><span class="nx">Server</span><span class="p">)</span><span class="w"> </span><span class="nf">ListenAndServe</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// ... listener setup ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">l</span><span class="p">.</span><span class="nf">Accept</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nf">handleConnection</span><span class="p">(</span><span class="nx">conn</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">slog</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;http error: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="o">*</span><span class="nx">Server</span><span class="p">)</span><span class="w"> </span><span class="nf">handleConnection</span><span class="p">(</span><span class="nx">conn</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nx">Conn</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// handleRequest does the work of reading and responding</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">shouldClose</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nf">handleRequest</span><span class="p">(</span><span class="nx">conn</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="c1">// io.EOF is a normal way for a persistent connection to end.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">Is</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">EOF</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">shouldClose</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="c1">// Client requested a close, so we exit the loop.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Default to keeping the connection alive for HTTP/1.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">req</span><span class="p">.</span><span class="nx">Close</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">false</span><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// Check if the client or a previous handler wants to close the connection.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">switch</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nf">ToLower</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;Connection&#34;</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">case</span><span class="w"> </span><span class="s">&#34;close&#34;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">req</span><span class="p">.</span><span class="nx">Close</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">case</span><span class="w"> </span><span class="s">&#34;keep-alive&#34;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// This is the default, but we handle it explicitly.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">req</span><span class="p">.</span><span class="nx">Close</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">[</span><span class="s">&#34;Host&#34;</span><span class="p">];</span><span class="w"> </span><span class="p">!</span><span class="nx">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// We send an error response here in a real implementation.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;required &#39;Host&#39; header not found&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">chunkedBodyReader</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">reader</span><span class="w"> </span><span class="o">*</span><span class="nx">bufio</span><span class="p">.</span><span class="nx">Reader</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">n</span><span class="w">      </span><span class="kt">int64</span><span class="w"> </span><span class="c1">// bytes left in current chunk</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">err</span><span class="w">    </span><span class="kt">error</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">chunkedBodyReader</span><span class="p">)</span><span class="w"> </span><span class="nf">Read</span><span class="p">(</span><span class="nx">p</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nx">n</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Do we need to read the size of the next chunk?</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">n</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">r</span><span class="p">.</span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nf">readChunkSize</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// If the next chunk size is 0, we&#39;re at the end.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">n</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">EOF</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ... logic to read from the current chunk ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">writeHeader</span><span class="p">(</span><span class="nx">conn</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span><span class="p">,</span><span class="w"> </span><span class="nx">proto</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">headers</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">Header</span><span class="p">,</span><span class="w"> </span><span class="nx">statusCode</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">clSet</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">headers</span><span class="p">[</span><span class="s">&#34;Content-Length&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">teSet</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">headers</span><span class="p">[</span><span class="s">&#34;Transfer-Encoding&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// If no length is set, we decide to use chunking.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">clSet</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="p">!</span><span class="nx">teSet</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">r</span><span class="p">.</span><span class="nx">chunkedEncoding</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">r</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;Transfer-Encoding&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;chunked&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ... write headers ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">Write</span><span class="p">(</span><span class="nx">b</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// ... ensure headers are written ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">chunkedEncoding</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// Write the chunk size in hex, followed by \r\n</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">chunkSize</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;%x\r\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">b</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="nx">chunkSize</span><span class="p">));</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Write the actual chunk data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">chunkedEncoding</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// Write the trailing \r\n for the chunk</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">nlcf</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl --http1.1 -v http://127.0.0.1:9000/headers http://127.0.0.1:9000/status/204
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">*   Trying 127.0.0.1:9000...
</span></span><span class="line"><span class="cl">* Connected to 127.0.0.1 <span class="o">(</span>127.0.0.1<span class="o">)</span> port <span class="m">9000</span> <span class="o">(</span><span class="c1">#0)</span>
</span></span><span class="line"><span class="cl">&gt; GET /headers HTTP/1.1
</span></span><span class="line"><span class="cl">&gt; Host: 127.0.0.1:9000
</span></span><span class="line"><span class="cl">&gt; ...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">&lt; HTTP/1.1 <span class="m">200</span> OK
</span></span><span class="line"><span class="cl">&lt; Connection: keep-alive
</span></span><span class="line"><span class="cl">&lt; ...
</span></span><span class="line"><span class="cl">* Connection <span class="c1">#0 to host 127.0.0.1 left intact</span>
</span></span><span class="line"><span class="cl">* Found bundle <span class="k">for</span> host 127.0.0.1: 0x1400084c0 <span class="o">[</span>can pipeline<span class="o">]</span>
</span></span><span class="line"><span class="cl">* Re-using existing connection! <span class="o">(</span><span class="c1">#0) with host 127.0.0.1</span>
</span></span><span class="line"><span class="cl">&gt; GET /status/204 HTTP/1.1
</span></span><span class="line"><span class="cl">&gt; Host: 127.0.0.1:9000
</span></span><span class="line"><span class="cl">&gt; ...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">&lt; HTTP/1.1 <span class="m">204</span> No Content
</span></span><span class="line"><span class="cl">&lt; Connection: keep-alive
</span></span><span class="line"><span class="cl">&lt; ...
</span></span><span class="line"><span class="cl">* Connection <span class="c1">#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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">printf</span> <span class="s2">&#34;GET / HTTP/1.1\r\n\r\n&#34;</span> <span class="p">|</span> nc localhost <span class="m">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" class="chroma"><code class="language-plaintext" data-lang="plaintext"><span class="line"><span class="cl">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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">printf</span> <span class="s2">&#34;GET /headers HTTP/1.1\r\nHost: localhost\r\n\r\n&#34;</span> <span class="p">|</span> nc localhost <span class="m">9000</span>
</span></span><span class="line"><span class="cl">HTTP/1.1 <span class="m">200</span> OK
</span></span><span class="line"><span class="cl">Connection: keep-alive
</span></span><span class="line"><span class="cl">Content-Type: application/json
</span></span><span class="line"><span class="cl">Transfer-Encoding: chunked
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="m">61</span>
</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;Accept-Encoding&#34;</span>:<span class="o">[</span><span class="s2">&#34;gzip&#34;</span><span class="o">]</span>,<span class="s2">&#34;Host&#34;</span>:<span class="o">[</span><span class="s2">&#34;localhost&#34;</span><span class="o">]</span>,<span class="s2">&#34;User-Agent&#34;</span>:<span class="o">[</span><span class="s2">&#34;Go-http-client/1.1&#34;</span><span class="o">]}</span>
</span></span><span class="line"><span class="cl"><span class="m">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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># We send two chunks: &#34;hello&#34; and &#34; world&#34;</span>
</span></span><span class="line"><span class="cl">$ <span class="o">(</span>
</span></span><span class="line"><span class="cl">  <span class="nb">printf</span> <span class="s2">&#34;5\r\nhello\r\n&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  sleep 1<span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">printf</span> <span class="s2">&#34;6\r\n world\r\n&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  sleep 1<span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">printf</span> <span class="s2">&#34;0\r\n\r\n&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="o">)</span> <span class="p">|</span> curl --http1.1 -X POST --header <span class="s2">&#34;Transfer-Encoding: chunked&#34;</span> -T - http://127.0.0.1:9000/echo/chunked
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;bufio&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;bytes&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;context&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;encoding/json&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;errors&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;io&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;log&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;log/slog&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;math&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;net&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;net/http&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;net/url&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;strconv&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;strings&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">var</span><span class="w"> </span><span class="nx">nlcf</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="p">{</span><span class="mh">0x0d</span><span class="p">,</span><span class="w"> </span><span class="mh">0x0a</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// Server is a simple HTTP/1.1 server.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">type</span><span class="w"> </span><span class="nx">Server</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Addr</span><span class="w">    </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Handler</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">Handler</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// ListenAndServe starts the server.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="o">*</span><span class="nx">Server</span><span class="p">)</span><span class="w"> </span><span class="nf">ListenAndServe</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">handler</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nx">Handler</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">handler</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">handler</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">DefaultServeMux</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">l</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nf">Listen</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nx">Addr</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">l</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">l</span><span class="p">.</span><span class="nf">Accept</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nf">handleConnection</span><span class="p">(</span><span class="nx">conn</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">slog</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;http error: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="o">*</span><span class="nx">Server</span><span class="p">)</span><span class="w"> </span><span class="nf">handleConnection</span><span class="p">(</span><span class="nx">conn</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nx">Conn</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">shouldClose</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nf">handleRequest</span><span class="p">(</span><span class="nx">conn</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">Is</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">EOF</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">shouldClose</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="o">*</span><span class="nx">Server</span><span class="p">)</span><span class="w"> </span><span class="nf">handleRequest</span><span class="p">(</span><span class="nx">conn</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nx">Conn</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">bool</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Limit headers to 1MB</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">limitReader</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">LimitReader</span><span class="p">(</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="o">*</span><span class="mi">1024</span><span class="o">*</span><span class="mi">1024</span><span class="p">).(</span><span class="o">*</span><span class="nx">io</span><span class="p">.</span><span class="nx">LimitedReader</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">reader</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">bufio</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">(</span><span class="nx">limitReader</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">reqLineBytes</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reader</span><span class="p">.</span><span class="nf">ReadLine</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;read request line error: %w&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">reqLine</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">string</span><span class="p">(</span><span class="nx">reqLineBytes</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">new</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">found</span><span class="w"> </span><span class="kt">bool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">Method</span><span class="p">,</span><span class="w"> </span><span class="nx">reqLine</span><span class="p">,</span><span class="w"> </span><span class="nx">found</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nf">Cut</span><span class="p">(</span><span class="nx">reqLine</span><span class="p">,</span><span class="w"> </span><span class="s">&#34; &#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">found</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;invalid method&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nf">methodValid</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">Method</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;invalid method&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">RequestURI</span><span class="p">,</span><span class="w"> </span><span class="nx">reqLine</span><span class="p">,</span><span class="w"> </span><span class="nx">found</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nf">Cut</span><span class="p">(</span><span class="nx">reqLine</span><span class="p">,</span><span class="w"> </span><span class="s">&#34; &#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">found</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;invalid path&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">URL</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">url</span><span class="p">.</span><span class="nf">ParseRequestURI</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">RequestURI</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;invalid path: %w&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">Proto</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">reqLine</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">ProtoMajor</span><span class="p">,</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">ProtoMinor</span><span class="p">,</span><span class="w"> </span><span class="nx">found</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nf">parseProtocol</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">Proto</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">found</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;invalid protocol&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">Header</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">line</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reader</span><span class="p">.</span><span class="nf">ReadLine</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">EOF</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">break</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">line</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">break</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">k</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">bytes</span><span class="p">.</span><span class="nf">Cut</span><span class="p">(</span><span class="nx">line</span><span class="p">,</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="p">{</span><span class="sc">&#39;:&#39;</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;invalid header&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">ToLower</span><span class="p">(</span><span class="nb">string</span><span class="p">(</span><span class="nx">k</span><span class="p">)),</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nf">TrimLeft</span><span class="p">(</span><span class="nb">string</span><span class="p">(</span><span class="nx">v</span><span class="p">),</span><span class="w"> </span><span class="s">&#34; &#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">[</span><span class="s">&#34;Host&#34;</span><span class="p">];</span><span class="w"> </span><span class="p">!</span><span class="nx">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;required &#39;Host&#39; header not found&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">switch</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nf">ToLower</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;Connection&#34;</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="s">&#34;keep-alive&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">req</span><span class="p">.</span><span class="nx">Close</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="s">&#34;close&#34;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">req</span><span class="p">.</span><span class="nx">Close</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">limitReader</span><span class="p">.</span><span class="nx">N</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">math</span><span class="p">.</span><span class="nx">MaxInt64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ctx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ctx</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithValue</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">LocalAddrContextKey</span><span class="p">,</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">LocalAddr</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancelCtx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithCancel</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nf">cancelCtx</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">contentLength</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">parseContentLength</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;Content-Length&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">ContentLength</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">contentLength</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">isChunked</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;Transfer-Encoding&#34;</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s">&#34;chunked&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">ContentLength</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="p">!</span><span class="nx">isChunked</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">req</span><span class="p">.</span><span class="nx">Body</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">noBody</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">isChunked</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">req</span><span class="p">.</span><span class="nx">Body</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">chunkedBodyReader</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">reader</span><span class="p">:</span><span class="w"> </span><span class="nx">reader</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">req</span><span class="p">.</span><span class="nx">Body</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">bodyReader</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">reader</span><span class="p">:</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">LimitReader</span><span class="p">(</span><span class="nx">reader</span><span class="p">,</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">ContentLength</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">RemoteAddr</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">RemoteAddr</span><span class="p">().</span><span class="nf">String</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">w</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">responseBodyWriter</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">req</span><span class="p">:</span><span class="w">     </span><span class="nx">req</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">conn</span><span class="p">:</span><span class="w">    </span><span class="nx">conn</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">headers</span><span class="p">:</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">Header</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">s</span><span class="p">.</span><span class="nx">Handler</span><span class="p">.</span><span class="nf">ServeHTTP</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nf">WithContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">w</span><span class="p">.</span><span class="nf">flush</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">Close</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">type</span><span class="w"> </span><span class="nx">noBody</span><span class="w"> </span><span class="kd">struct</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">noBody</span><span class="p">)</span><span class="w"> </span><span class="nf">Read</span><span class="p">([]</span><span class="kt">byte</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">EOF</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">noBody</span><span class="p">)</span><span class="w"> </span><span class="nf">Close</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w">             </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">parseContentLength</span><span class="p">(</span><span class="nx">headerval</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">int64</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">headerval</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">strconv</span><span class="p">.</span><span class="nf">ParseInt</span><span class="p">(</span><span class="nx">headerval</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">64</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">parseProtocol</span><span class="p">(</span><span class="nx">proto</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="kt">bool</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">switch</span><span class="w"> </span><span class="nx">proto</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="s">&#34;HTTP/1.0&#34;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="s">&#34;HTTP/1.1&#34;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">methodValid</span><span class="p">(</span><span class="nx">method</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">switch</span><span class="w"> </span><span class="nx">method</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodGet</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodHead</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodPost</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodPut</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodPatch</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodDelete</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodConnect</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodOptions</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodTrace</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">type</span><span class="w"> </span><span class="nx">bodyReader</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">reader</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">Reader</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">bodyReader</span><span class="p">)</span><span class="w"> </span><span class="nf">Read</span><span class="p">(</span><span class="nx">p</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nx">n</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">reader</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="nx">p</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">bodyReader</span><span class="p">)</span><span class="w"> </span><span class="nf">Close</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">Copy</span><span class="p">(</span><span class="nx">io</span><span class="p">.</span><span class="nx">Discard</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">reader</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">type</span><span class="w"> </span><span class="nx">chunkedBodyReader</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">reader</span><span class="w"> </span><span class="o">*</span><span class="nx">bufio</span><span class="p">.</span><span class="nx">Reader</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">n</span><span class="w">      </span><span class="kt">int64</span><span class="w"> </span><span class="c1">// bytes left in current chunk</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">err</span><span class="w">    </span><span class="kt">error</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">chunkedBodyReader</span><span class="p">)</span><span class="w"> </span><span class="nf">Read</span><span class="p">(</span><span class="nx">p</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nx">n</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">n</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">r</span><span class="p">.</span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nf">readChunkSize</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">n</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">EOF</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nb">int64</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="nx">p</span><span class="p">))</span><span class="w"> </span><span class="p">&gt;</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">n</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">p</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">p</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="nx">r</span><span class="p">.</span><span class="nx">n</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">reader</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="nx">p</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">r</span><span class="p">.</span><span class="nx">n</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="nb">int64</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">n</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// Read trailing \r\n</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">b</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">reader</span><span class="p">.</span><span class="nf">ReadByte</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">b</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="sc">&#39;\r&#39;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;missing \r after chunk&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">b</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">reader</span><span class="p">.</span><span class="nf">ReadByte</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">b</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="sc">&#39;\n&#39;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;missing \n after chunk&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">r</span><span class="p">.</span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">chunkedBodyReader</span><span class="p">)</span><span class="w"> </span><span class="nf">readChunkSize</span><span class="p">()</span><span class="w"> </span><span class="p">(</span><span class="kt">int64</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">line</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nf">readLine</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// chunkSize is hex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">strconv</span><span class="p">.</span><span class="nf">ParseInt</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">TrimSpace</span><span class="p">(</span><span class="nb">string</span><span class="p">(</span><span class="nx">line</span><span class="p">)),</span><span class="w"> </span><span class="mi">16</span><span class="p">,</span><span class="w"> </span><span class="mi">64</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// Read trailers</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">line</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nf">readLine</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">line</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">break</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">chunkedBodyReader</span><span class="p">)</span><span class="w"> </span><span class="nf">readLine</span><span class="p">()</span><span class="w"> </span><span class="p">(</span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">line</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">b</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">reader</span><span class="p">.</span><span class="nf">ReadByte</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">b</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="sc">&#39;\n&#39;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">break</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">line</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">append</span><span class="p">(</span><span class="nx">line</span><span class="p">,</span><span class="w"> </span><span class="nx">b</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nf">TrimRight</span><span class="p">(</span><span class="nb">string</span><span class="p">(</span><span class="nx">line</span><span class="p">),</span><span class="w"> </span><span class="s">&#34;\r&#34;</span><span class="p">),</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">chunkedBodyReader</span><span class="p">)</span><span class="w"> </span><span class="nf">Close</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">Copy</span><span class="p">(</span><span class="nx">io</span><span class="p">.</span><span class="nx">Discard</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">type</span><span class="w"> </span><span class="nx">responseBodyWriter</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="w">             </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">conn</span><span class="w">            </span><span class="nx">net</span><span class="p">.</span><span class="nx">Conn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">sentHeaders</span><span class="w">     </span><span class="kt">bool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">headers</span><span class="w">         </span><span class="nx">http</span><span class="p">.</span><span class="nx">Header</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">chunkedEncoding</span><span class="w"> </span><span class="kt">bool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">bodyBuffer</span><span class="w">      </span><span class="o">*</span><span class="nx">bytes</span><span class="p">.</span><span class="nx">Buffer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">Header</span><span class="p">()</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">Header</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">headers</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">Write</span><span class="p">(</span><span class="nx">b</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">r</span><span class="p">.</span><span class="nx">sentHeaders</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;Content-Type&#34;</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">r</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;Content-Type&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">DetectContentType</span><span class="p">(</span><span class="nx">b</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">r</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">chunkedEncoding</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">chunkSize</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;%x\r\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">b</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="nx">chunkSize</span><span class="p">));</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">chunkedEncoding</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">nlcf</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">Flush</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">r</span><span class="p">.</span><span class="nx">sentHeaders</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">r</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">flusher</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.(</span><span class="kd">interface</span><span class="p">{</span><span class="w"> </span><span class="nf">Flush</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">});</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">flusher</span><span class="p">.</span><span class="nf">Flush</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">flush</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">chunkedEncoding</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="s">&#34;0\r\n\r\n&#34;</span><span class="p">));</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">r</span><span class="p">.</span><span class="nf">writeBufferedBody</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">statusCode</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">sentHeaders</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">slog</span><span class="p">.</span><span class="nf">Warn</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;WriteHeader called twice, second time with: %d&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">statusCode</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">r</span><span class="p">.</span><span class="nf">writeHeader</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">req</span><span class="p">.</span><span class="nx">Proto</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">headers</span><span class="p">,</span><span class="w"> </span><span class="nx">statusCode</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">r</span><span class="p">.</span><span class="nx">sentHeaders</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">r</span><span class="p">.</span><span class="nf">writeBufferedBody</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">writeBufferedBody</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">bodyBuffer</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">bodyBuffer</span><span class="p">.</span><span class="nf">Bytes</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">slog</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="s">&#34;Error writing buffered body&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;err&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">r</span><span class="p">.</span><span class="nx">bodyBuffer</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">writeHeader</span><span class="p">(</span><span class="nx">conn</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span><span class="p">,</span><span class="w"> </span><span class="nx">proto</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">headers</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">Header</span><span class="p">,</span><span class="w"> </span><span class="nx">statusCode</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">clSet</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">headers</span><span class="p">[</span><span class="s">&#34;Content-Length&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">teSet</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">headers</span><span class="p">[</span><span class="s">&#34;Transfer-Encoding&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">clSet</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="p">!</span><span class="nx">teSet</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">r</span><span class="p">.</span><span class="nx">chunkedEncoding</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">r</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;Transfer-Encoding&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;chunked&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">req</span><span class="p">.</span><span class="nx">Close</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">r</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;Connection&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;close&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">r</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;Connection&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;keep-alive&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">proto</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="kt">byte</span><span class="p">{</span><span class="sc">&#39; &#39;</span><span class="p">});</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">strconv</span><span class="p">.</span><span class="nf">FormatInt</span><span class="p">(</span><span class="nb">int64</span><span class="p">(</span><span class="nx">statusCode</span><span class="p">),</span><span class="w"> </span><span class="mi">10</span><span class="p">));</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="kt">byte</span><span class="p">{</span><span class="sc">&#39; &#39;</span><span class="p">});</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">StatusText</span><span class="p">(</span><span class="nx">statusCode</span><span class="p">));</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">nlcf</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">k</span><span class="p">,</span><span class="w"> </span><span class="nx">vals</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">headers</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">val</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">vals</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">k</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="kt">byte</span><span class="p">{</span><span class="sc">&#39;:&#39;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39; &#39;</span><span class="p">});</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">val</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">nlcf</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">nlcf</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">addr</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;127.0.0.1:9000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">NewServeMux</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="p">.</span><span class="nf">Handle</span><span class="p">(</span><span class="s">&#34;/&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">FileServer</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nf">Dir</span><span class="p">(</span><span class="s">&#34;.&#34;</span><span class="p">)))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/echo&#34;</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">defer</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">Body</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">b</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">ReadAll</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">Body</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="mi">400</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">w</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/echo/chunked&#34;</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">defer</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">Body</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">io</span><span class="p">.</span><span class="nf">Copy</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">Body</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/status/{status}&#34;</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">status</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">strconv</span><span class="p">.</span><span class="nf">ParseInt</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nf">PathValue</span><span class="p">(</span><span class="s">&#34;status&#34;</span><span class="p">),</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">64</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusBadRequest</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">io</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;error: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="nx">status</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/headers&#34;</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">w</span><span class="p">.</span><span class="nf">Header</span><span class="p">().</span><span class="nf">Add</span><span class="p">(</span><span class="s">&#34;content-type&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;application/json&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">json</span><span class="p">.</span><span class="nf">NewEncoder</span><span class="p">(</span><span class="nx">w</span><span class="p">).</span><span class="nf">Encode</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">Header</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/nothing&#34;</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">s</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">Server</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Addr</span><span class="p">:</span><span class="w">    </span><span class="nx">addr</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Handler</span><span class="p">:</span><span class="w"> </span><span class="nx">mux</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;Starting web server: http://%s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">addr</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div>
</details>
</p>
]]></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" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Create a dummy log file that&#39;s roughly 6MB.</span>
</span></span><span class="line"><span class="cl">yes <span class="s2">&#34;this is a line in our log file&#34;</span> <span class="p">|</span> head -n <span class="m">200000</span> &gt; log.txt
</span></span><span class="line"><span class="cl"><span class="c1"># (`yes` just prints the same line endlessly — we use `head` to cap it.)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Check its size</span>
</span></span><span class="line"><span class="cl">ls -lh log.txt
</span></span><span class="line"><span class="cl"><span class="c1"># 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" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 1. Encrypt the log file (with GPG&#39;s internal compression DISABLED)</span>
</span></span><span class="line"><span class="cl">gpg <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --batch --yes <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --pinentry-mode loopback <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --passphrase <span class="s2">&#34;test&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --compress-algo none <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --symmetric <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --output encrypted_first.gpg <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  log.txt
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 2. Now, try to compress the encrypted file</span>
</span></span><span class="line"><span class="cl">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" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ls -lh log.txt encrypted_first.gpg encrypted_then_compressed.gz
</span></span><span class="line"><span class="cl"><span class="c1"># Output:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># -rw-r--r--@ 1 kevin  staff   5.9M Dec 12 17:13 log.txt</span>
</span></span><span class="line"><span class="cl"><span class="c1"># -rw-r--r--@ 1 kevin  staff   5.9M Dec 12 18:14 encrypted_first.gpg</span>
</span></span><span class="line"><span class="cl"><span class="c1"># -rw-r--r--@ 1 kevin  staff   5.9M Dec 12 18:14 encrypted_then_compressed.gz</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">ls -l log.txt encrypted_first.gpg encrypted_then_compressed.gz
</span></span><span class="line"><span class="cl"><span class="c1"># Output:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># -rw-r--r--@ 1 kevin  staff  6200000 Dec 12 17:13 log.txt</span>
</span></span><span class="line"><span class="cl"><span class="c1"># -rw-r--r--@ 1 kevin  staff  6200084 Dec 12 18:14 encrypted_first.gpg</span>
</span></span><span class="line"><span class="cl"><span class="c1"># -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" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 1. Compress the raw log file</span>
</span></span><span class="line"><span class="cl">gzip -c log.txt &gt; compressed_first.gz
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 2. Encrypt the compressed file</span>
</span></span><span class="line"><span class="cl">gpg <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --batch --yes <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --pinentry-mode loopback <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --passphrase <span class="s2">&#34;test&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --symmetric <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --output compressed_then_encrypted.gpg <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  compressed_first.gz
</span></span></code></pre></div><p><strong>What you see</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ls -lh log.txt compressed_first.gz compressed_then_encrypted.gpg
</span></span><span class="line"><span class="cl"><span class="c1"># Output:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># -rw-r--r--@ 1 kevin  staff   5.9M Dec 12 17:13 log.txt</span>
</span></span><span class="line"><span class="cl"><span class="c1"># -rw-r--r--@ 1 kevin  staff    15K Dec 12 18:15 compressed_first.gz</span>
</span></span><span class="line"><span class="cl"><span class="c1"># -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" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ls -lh encrypted_then_compressed.gz compressed_then_encrypted.gpg
</span></span><span class="line"><span class="cl"><span class="c1"># Output:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># -rw-r--r--@ 1 kevin  staff    15K Dec 12 18:15 compressed_then_encrypted.gpg</span>
</span></span><span class="line"><span class="cl"><span class="c1"># -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"
             alt="" 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>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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;io&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;log&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;net/http&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">queryHandler</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// The new ServeMux handles method checking based on registration.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">body</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">ReadAll</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">Body</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">http</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Error reading request body&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusInternalServerError</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">Body</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// In a real application, you&#39;d parse the query from the body</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// (e.g., JSON) and fetch data accordingly.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Fprintf</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Received your QUERY request with body: %s\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nb">string</span><span class="p">(</span><span class="nx">body</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;Handled QUERY request with body: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nb">string</span><span class="p">(</span><span class="nx">body</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">NewServeMux</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Register handler specifically for QUERY method on /data path</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;QUERY /data&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">queryHandler</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Server starting on port 8080, handling custom QUERY method...&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">(</span><span class="s">&#34;:8080&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">mux</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;io&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;log&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;net/http&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;strings&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">queryPayload</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">`{&#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 class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">client</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">http</span><span class="p">.</span><span class="nx">Client</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Create a new request with the custom &#34;QUERY&#34; method</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">NewRequest</span><span class="p">(</span><span class="s">&#34;QUERY&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;http://localhost:8080/data&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">(</span><span class="nx">queryPayload</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;Error creating request: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;Content-Type&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;application/json&#34;</span><span class="p">)</span><span class="w"> </span><span class="c1">// Important for body processing</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">resp</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">client</span><span class="p">.</span><span class="nf">Do</span><span class="p">(</span><span class="nx">req</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;Error sending request: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Response Status:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">Status</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">body</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">ReadAll</span><span class="p">(</span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;Error reading response body: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Response Body:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nb">string</span><span class="p">(</span><span class="nx">body</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl -X QUERY -d <span class="s1">&#39;{&#34;filters&#34;: {&#34;status&#34;: &#34;active&#34;, &#34;category&#34;: &#34;electronics&#34;}, &#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">fields&#34;: [&#34;name&#34;, &#34;price&#34;]}&#39;</span> http://localhost:8080/data -v
</span></span><span class="line"><span class="cl">* Host localhost:8080 was resolved.
</span></span><span class="line"><span class="cl">* IPv6: ::1
</span></span><span class="line"><span class="cl">* IPv4: 127.0.0.1
</span></span><span class="line"><span class="cl">*   Trying <span class="o">[</span>::1<span class="o">]</span>:8080...
</span></span><span class="line"><span class="cl">* Connected to localhost <span class="o">(</span>::1<span class="o">)</span> port <span class="m">8080</span>
</span></span><span class="line"><span class="cl">&gt; QUERY /data HTTP/1.1
</span></span><span class="line"><span class="cl">&gt; Host: localhost:8080
</span></span><span class="line"><span class="cl">&gt; User-Agent: curl/8.7.1
</span></span><span class="line"><span class="cl">&gt; Accept: */*
</span></span><span class="line"><span class="cl">&gt; Content-Length: <span class="m">89</span>
</span></span><span class="line"><span class="cl">&gt; Content-Type: application/x-www-form-urlencoded
</span></span><span class="line"><span class="cl">&gt; 
</span></span><span class="line"><span class="cl">* upload completely sent off: <span class="m">89</span> bytes
</span></span><span class="line"><span class="cl">&lt; HTTP/1.1 <span class="m">200</span> OK
</span></span><span class="line"><span class="cl">&lt; Date: Sat, <span class="m">31</span> May <span class="m">2025</span> 15:47:45 GMT
</span></span><span class="line"><span class="cl">&lt; Content-Length: <span class="m">129</span>
</span></span><span class="line"><span class="cl">&lt; Content-Type: text/plain<span class="p">;</span> <span class="nv">charset</span><span class="o">=</span>utf-8
</span></span><span class="line"><span class="cl">&lt; 
</span></span><span class="line"><span class="cl">Received your QUERY request with body: <span class="o">{</span><span class="s2">&#34;filters&#34;</span>: <span class="o">{</span><span class="s2">&#34;status&#34;</span>: <span class="s2">&#34;active&#34;</span>, <span class="s2">&#34;category&#34;</span>: <span class="s2">&#34;electronics&#34;</span><span class="o">}</span>, <span class="s2">&#34;fields&#34;</span>: <span class="o">[</span><span class="s2">&#34;name&#34;</span>, <span class="s2">&#34;price&#34;</span><span class="o">]}</span>
</span></span><span class="line"><span class="cl">* Connection <span class="c1">#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>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" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="kd">enum</span> <span class="n">FooBar</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="n">FOO_BAR_UNSPECIFIED</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="n">FOO_BAR_FIRST_VALUE</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="n">FOO_BAR_SECOND_VALUE</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</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" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="kd">message</span> <span class="nc">Hello</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span></code></pre></div><p>Here&rsquo;s the type generated by protoc:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Hello</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">state</span><span class="w">         </span><span class="nx">protoimpl</span><span class="p">.</span><span class="nx">MessageState</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">sizeCache</span><span class="w">     </span><span class="nx">protoimpl</span><span class="p">.</span><span class="nx">SizeCache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">unknownFields</span><span class="w"> </span><span class="nx">protoimpl</span><span class="p">.</span><span class="nx">UnknownFields</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Name</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="s">`protobuf:&#34;bytes,1,opt,name=name,proto3&#34; json:&#34;name,omitempty&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// With these methods, contents are stripped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="nx">Hello</span><span class="p">)</span><span class="w"> </span><span class="nf">Reset</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="nx">Hello</span><span class="p">)</span><span class="w"> </span><span class="nf">String</span><span class="p">()</span><span class="w"> </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="nx">Hello</span><span class="p">)</span><span class="w"> </span><span class="nf">ProtoMessage</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="nx">Hello</span><span class="p">)</span><span class="w"> </span><span class="nf">ProtoReflect</span><span class="p">()</span><span class="w"> </span><span class="nx">protoreflect</span><span class="p">.</span><span class="nx">Message</span><span class="w">
</span></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" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="kd">message</span> <span class="nc">User</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">int32</span> <span class="n">age</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// required.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span><span class="err">
</span></span></span></code></pre></div><p>I do like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="kd">message</span> <span class="nc">User</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">int32</span> <span class="n">age</span> <span class="o">=</span> <span class="mi">1</span> <span class="p">[(</span><span class="n">buf.validate.field</span><span class="p">)</span><span class="o">.</span><span class="k">required</span> <span class="o">=</span> <span class="kc">true</span><span class="p">];</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</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"
             alt="" 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"
             alt="" 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"
             alt="" 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"
             alt="" 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"
             alt="" 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"
             alt="" 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>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="https://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"
             alt="" 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" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">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" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="n">syntax</span> <span class="o">=</span> <span class="s">&#34;proto3&#34;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kn">package</span> <span class="nn">greet</span><span class="o">.</span><span class="n">v1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">GreetRequest</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">GreetResponse</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">greeting</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">service</span> <span class="n">GreetService</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">rpc</span> <span class="n">Greet</span><span class="p">(</span><span class="n">GreetRequest</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">GreetResponse</span><span class="p">)</span> <span class="p">{}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ buf build ./example.proto -o ./example.binpb
</span></span><span class="line"><span class="cl">$ fauxrpc run --schema<span class="o">=</span>./example.binpb
</span></span><span class="line"><span class="cl">2024/08/17 08:01:19 INFO Listening on http://127.0.0.1:6660
</span></span><span class="line"><span class="cl">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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ buf curl --http2-prior-knowledge http://127.0.0.1:6660/greet.v1.GreetService/Greet
</span></span><span class="line"><span class="cl"><span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;greeting&#34;</span>: <span class="s2">&#34;dream&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ fauxrpc run --schema<span class="o">=</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ buf build buf.build/bufbuild/registry -o bufbuild.registry.json
</span></span><span class="line"><span class="cl">$ fauxrpc run --schema<span class="o">=</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ fauxrpc run --schema<span class="o">=</span>https://demo.connectrpc.com --schema<span class="o">=</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" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="n">syntax</span> <span class="o">=</span> <span class="s">&#34;proto3&#34;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kn">package</span> <span class="nn">http</span><span class="o">.</span><span class="n">service</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">import</span> <span class="s">&#34;google/api/annotations.proto&#34;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">service</span> <span class="n">HTTPService</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">rpc</span> <span class="n">GetMessage</span><span class="p">(</span><span class="n">GetMessageRequest</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">Message</span><span class="p">)</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>    <span class="k">option</span> <span class="p">(</span><span class="n">google.api.http</span><span class="p">)</span> <span class="o">=</span> <span class="p">{</span><span class="n">get</span><span class="o">:</span> <span class="s">&#34;/v1/{name=messages/*}&#34;</span><span class="p">};</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">GetMessageRequest</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// Mapped to URL path.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">Message</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">text</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// The resource content.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span><span class="err">
</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" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$ buf build ./httpservice.proto -o ./httpservice.binpb
</span></span><span class="line"><span class="cl">$ 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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl http://127.0.0.1:6660/v1/messages/123456
</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;text&#34;</span>:<span class="s2">&#34;Retro.&#34;</span><span class="o">}</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" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="n">syntax</span> <span class="o">=</span> <span class="s">&#34;proto3&#34;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kn">package</span> <span class="nn">greet</span><span class="o">.</span><span class="n">v1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">GreetRequest</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">GreetResponse</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">greeting</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">service</span> <span class="n">GreetService</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">rpc</span> <span class="n">Greet</span><span class="p">(</span><span class="n">GreetRequest</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">GreetResponse</span><span class="p">)</span> <span class="p">{}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</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" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;greeting&#34;</span><span class="p">:</span> <span class="s2">&#34;sufficient&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</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" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="n">syntax</span> <span class="o">=</span> <span class="s">&#34;proto3&#34;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">import</span> <span class="s">&#34;buf/validate/validate.proto&#34;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kn">package</span> <span class="nn">greet</span><span class="o">.</span><span class="n">v1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">GreetRequest</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="mi">1</span> <span class="p">[(</span><span class="n">buf.validate.field</span><span class="p">)</span><span class="o">.</span><span class="kt">string</span> <span class="o">=</span> <span class="p">{</span><span class="n">min_len</span><span class="o">:</span> <span class="mi">3</span><span class="p">,</span> <span class="n">max_len</span><span class="o">:</span> <span class="mi">100</span><span class="p">}];</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">GreetResponse</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">greeting</span> <span class="o">=</span> <span class="mi">1</span> <span class="p">[(</span><span class="n">buf.validate.field</span><span class="p">)</span><span class="o">.</span><span class="kt">string</span><span class="o">.</span><span class="n">pattern</span> <span class="o">=</span> <span class="s">&#34;^Hello, [a-zA-Z]+$&#34;</span><span class="p">];</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">service</span> <span class="n">GreetService</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">rpc</span> <span class="n">Greet</span><span class="p">(</span><span class="n">GreetRequest</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">GreetResponse</span><span class="p">)</span> <span class="p">{}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</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" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;greeting&#34;</span><span class="p">:</span> <span class="s2">&#34;Hello, TWXxF&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</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" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="kd">message</span> <span class="nc">UserRef</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">option</span> <span class="p">(</span><span class="n">buf.registry.priv.extension.v1beta1.message</span><span class="p">)</span><span class="o">.</span><span class="n">request_only</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">oneof</span> <span class="n">value</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>    <span class="k">option</span> <span class="p">(</span><span class="n">buf.validate.oneof</span><span class="p">)</span><span class="o">.</span><span class="k">required</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>    <span class="c1">// The id of the User.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">string</span> <span class="n">id</span> <span class="o">=</span> <span class="mi">1</span> <span class="p">[(</span><span class="n">buf.validate.field</span><span class="p">)</span><span class="o">.</span><span class="kt">string</span><span class="o">.</span><span class="n">tuuid</span> <span class="o">=</span> <span class="kc">true</span><span class="p">];</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>    <span class="c1">// The name of the User.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="mi">2</span> <span class="p">[(</span><span class="n">buf.validate.field</span><span class="p">)</span><span class="o">.</span><span class="kt">string</span> <span class="o">=</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>      <span class="n">min_len</span><span class="o">:</span> <span class="mi">2</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>      <span class="n">max_len</span><span class="o">:</span> <span class="mi">32</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>      <span class="n">pattern</span><span class="o">:</span> <span class="s">&#34;^[a-z][a-z0-9-]*[a-z0-9]$&#34;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>    <span class="p">}];</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ buf build buf.build/bufbuild/registry -o bufbuild.registry.binpb
</span></span><span class="line"><span class="cl">$ fauxrpc run --schema<span class="o">=</span>./bufbuild.registry.binpb
</span></span><span class="line"><span class="cl">$ buf curl --http2-prior-knowledge http://127.0.0.1:6660/buf.registry.owner.v1.UserService/ListUsers
</span></span><span class="line"><span class="cl"><span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;nextPageToken&#34;</span>: <span class="s2">&#34;Food truck.&#34;</span>,
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;users&#34;</span>: <span class="o">[</span>
</span></span><span class="line"><span class="cl">    <span class="o">{</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;id&#34;</span>: <span class="s2">&#34;c4468393f926400d8880a264df9c284a&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;createTime&#34;</span>: <span class="s2">&#34;2012-03-06T12:15:03.239463070Z&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;updateTime&#34;</span>: <span class="s2">&#34;1990-10-29T13:12:31.224347086Z&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;name&#34;</span>: <span class="s2">&#34;jexox&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;type&#34;</span>: <span class="s2">&#34;USER_TYPE_STANDARD&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;description&#34;</span>: <span class="s2">&#34;Tattooed taxidermy.&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;url&#34;</span>: <span class="s2">&#34;http://www.productexploit.name/synergies/target&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>,
</span></span><span class="line"><span class="cl">    <span class="o">{</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;id&#34;</span>: <span class="s2">&#34;0e4ca24f4ff54761b109daab0da1bea2&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;createTime&#34;</span>: <span class="s2">&#34;1955-05-16T02:37:30.643378679Z&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;updateTime&#34;</span>: <span class="s2">&#34;1923-08-28T04:28:43.330711919Z&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;name&#34;</span>: <span class="s2">&#34;ya0&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;type&#34;</span>: <span class="s2">&#34;USER_TYPE_STANDARD&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;state&#34;</span>: <span class="s2">&#34;USER_STATE_INACTIVE&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;description&#34;</span>: <span class="s2">&#34;Helvetica.&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;url&#34;</span>: <span class="s2">&#34;https://www.centralengage.info/markets/scale/e-commerce/exploit&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;verificationStatus&#34;</span>: <span class="s2">&#34;USER_VERIFICATION_STATUS_UNVERIFIED&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">  <span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="o">}</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="https://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" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="nf">GET</span> <span class="nn">/index.html</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.0</span>
</span></span><span class="line"><span class="cl"><span class="n">User-Agent</span><span class="o">:</span> <span class="l">Mozilla/5.0</span>
</span></span><span class="line"><span class="cl"><span class="n">Host</span><span class="o">:</span> <span class="l">www.example.com</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="g">(Optional request body)
</span></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" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.0</span> <span class="m">200</span> <span class="ne">OK</span>
</span></span><span class="line"><span class="cl"><span class="n">Content-Type</span><span class="o">:</span> <span class="l">text/html</span>
</span></span><span class="line"><span class="cl"><span class="n">Content-Length</span><span class="o">:</span> <span class="l">1354</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">(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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Server</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Addr</span><span class="w">    </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Handler</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">Handler</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="o">*</span><span class="nx">Server</span><span class="p">)</span><span class="w"> </span><span class="nf">ListenAndServe</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">handler</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nx">Handler</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">handler</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">handler</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">DefaultServeMux</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">l</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nf">Listen</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nx">Addr</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">l</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">l</span><span class="p">.</span><span class="nf">Accept</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nf">handleConnection</span><span class="p">(</span><span class="nx">conn</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">slog</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;http error: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="o">*</span><span class="nx">Server</span><span class="p">)</span><span class="w"> </span><span class="nf">handleConnection</span><span class="p">(</span><span class="nx">conn</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nx">Conn</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Limit headers to 1MB</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">limitReader</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">LimitReader</span><span class="p">(</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="o">*</span><span class="mi">1024</span><span class="o">*</span><span class="mi">1024</span><span class="p">).(</span><span class="o">*</span><span class="nx">io</span><span class="p">.</span><span class="nx">LimitedReader</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">reader</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">bufio</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">(</span><span class="nx">limitReader</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">headerReader</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">textproto</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">(</span><span class="nx">bufio</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">(</span><span class="nx">reader</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Read the request line: GET /path/to/index.html HTTP/1.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">reqLine</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">headerReader</span><span class="p">.</span><span class="nf">ReadLine</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;read request line error: %w&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">new</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">found</span><span class="w"> </span><span class="kt">bool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Parse Method: GET/POST/PUT/DELETE/etc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">Method</span><span class="p">,</span><span class="w"> </span><span class="nx">reqLine</span><span class="p">,</span><span class="w"> </span><span class="nx">found</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nf">Cut</span><span class="p">(</span><span class="nx">reqLine</span><span class="p">,</span><span class="w"> </span><span class="s">&#34; &#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">found</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;invalid method&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nf">methodValid</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">Method</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;invalid method&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Parse Request URI</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">RequestURI</span><span class="p">,</span><span class="w"> </span><span class="nx">reqLine</span><span class="p">,</span><span class="w"> </span><span class="nx">found</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nf">Cut</span><span class="p">(</span><span class="nx">reqLine</span><span class="p">,</span><span class="w"> </span><span class="s">&#34; &#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">found</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;invalid path&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">URL</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">url</span><span class="p">.</span><span class="nf">ParseRequestURI</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">RequestURI</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;invalid path: %w&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Parse protocol version &#34;HTTP/1.0&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">Proto</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">reqLine</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">ProtoMajor</span><span class="p">,</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">ProtoMinor</span><span class="p">,</span><span class="w"> </span><span class="nx">found</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nf">parseProtocol</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">Proto</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">found</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;invalid proto&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Parse headers</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">Header</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">line</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">headerReader</span><span class="p">.</span><span class="nf">ReadLineBytes</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">EOF</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">break</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">line</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">break</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">k</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">bytes</span><span class="p">.</span><span class="nf">Cut</span><span class="p">(</span><span class="nx">line</span><span class="p">,</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="p">{</span><span class="sc">&#39;:&#39;</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;invalid header&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">ToLower</span><span class="p">(</span><span class="nb">string</span><span class="p">(</span><span class="nx">k</span><span class="p">)),</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nf">TrimLeft</span><span class="p">(</span><span class="nb">string</span><span class="p">(</span><span class="nx">v</span><span class="p">),</span><span class="w"> </span><span class="s">&#34; &#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Unbound the limit after we&#39;ve read the headers since the body can be any size</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">limitReader</span><span class="p">.</span><span class="nx">N</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">math</span><span class="p">.</span><span class="nx">MaxInt64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ctx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ctx</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithValue</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">LocalAddrContextKey</span><span class="p">,</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">LocalAddr</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancelCtx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithCancel</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nf">cancelCtx</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">contentLength</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">parseContentLength</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;Content-Length&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">ContentLength</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">contentLength</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">ContentLength</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">req</span><span class="p">.</span><span class="nx">Body</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">noBody</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">req</span><span class="p">.</span><span class="nx">Body</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">bodyReader</span><span class="p">{</span><span class="nx">reader</span><span class="p">:</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">LimitReader</span><span class="p">(</span><span class="nx">reader</span><span class="p">,</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">ContentLength</span><span class="p">)}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">RemoteAddr</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">RemoteAddr</span><span class="p">().</span><span class="nf">String</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">Close</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="c1">// this is always true for HTTP/1.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">w</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">responseBodyWriter</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// We hard-code this because this is a HTTP/1.0 server.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// Web servers will make requests with HTTP/1.1 but</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// we&#39;re saying that we only support HTTP/1.0.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">proto</span><span class="p">:</span><span class="w">   </span><span class="s">&#34;HTTP/1.0&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">conn</span><span class="p">:</span><span class="w">    </span><span class="nx">conn</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">headers</span><span class="p">:</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">Header</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Finally, call our http.Handler!</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">s</span><span class="p">.</span><span class="nx">Handler</span><span class="p">.</span><span class="nf">ServeHTTP</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nf">WithContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">w</span><span class="p">.</span><span class="nx">sentHeaders</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">w</span><span class="p">.</span><span class="nf">sendHeaders</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">noBody</span><span class="w"> </span><span class="kd">struct</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">noBody</span><span class="p">)</span><span class="w"> </span><span class="nf">Read</span><span class="p">([]</span><span class="kt">byte</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w">         </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">EOF</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">noBody</span><span class="p">)</span><span class="w"> </span><span class="nf">Close</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w">                     </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">bodyReader</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">reader</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">Reader</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">bodyReader</span><span class="p">)</span><span class="w"> </span><span class="nf">Read</span><span class="p">(</span><span class="nx">p</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nx">n</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">reader</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="nx">p</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">bodyReader</span><span class="p">)</span><span class="w"> </span><span class="nf">Close</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">Copy</span><span class="p">(</span><span class="nx">io</span><span class="p">.</span><span class="nx">Discard</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">reader</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">parseContentLength</span><span class="p">(</span><span class="nx">headerval</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">int64</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">headerval</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">strconv</span><span class="p">.</span><span class="nf">ParseInt</span><span class="p">(</span><span class="nx">headerval</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">64</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">parseProtocol</span><span class="p">(</span><span class="nx">proto</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="kt">bool</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">switch</span><span class="w"> </span><span class="nx">proto</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="s">&#34;HTTP/1.0&#34;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="s">&#34;HTTP/1.1&#34;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">methodValid</span><span class="p">(</span><span class="nx">method</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">switch</span><span class="w"> </span><span class="nx">method</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodGet</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodHead</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodPost</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodPut</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodPatch</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodDelete</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodConnect</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodOptions</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">MethodTrace</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Handler</span><span class="w"> </span><span class="kd">interface</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nf">ServeHTTP</span><span class="p">(</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="o">*</span><span class="nx">Request</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Here&rsquo;s the implementation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">responseBodyWriter</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">proto</span><span class="w">       </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">conn</span><span class="w">        </span><span class="nx">net</span><span class="p">.</span><span class="nx">Conn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">sentHeaders</span><span class="w"> </span><span class="kt">bool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">headers</span><span class="w">     </span><span class="nx">http</span><span class="p">.</span><span class="nx">Header</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">Header</span><span class="p">()</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">Header</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">headers</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">Write</span><span class="p">(</span><span class="nx">b</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">r</span><span class="p">.</span><span class="nx">sentHeaders</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">r</span><span class="p">.</span><span class="nf">sendHeaders</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">statusCode</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">sentHeaders</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">slog</span><span class="p">.</span><span class="nf">Warn</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;WriteHeader called twice, second time with: %d&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">statusCode</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">r</span><span class="p">.</span><span class="nf">sendHeaders</span><span class="p">(</span><span class="nx">statusCode</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">sendHeaders</span><span class="p">(</span><span class="nx">statusCode</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">r</span><span class="p">.</span><span class="nx">sentHeaders</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">io</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">proto</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="kt">byte</span><span class="p">{</span><span class="sc">&#39; &#39;</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">io</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">strconv</span><span class="p">.</span><span class="nf">FormatInt</span><span class="p">(</span><span class="nb">int64</span><span class="p">(</span><span class="nx">statusCode</span><span class="p">),</span><span class="w"> </span><span class="mi">10</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="kt">byte</span><span class="p">{</span><span class="sc">&#39; &#39;</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">io</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">StatusText</span><span class="p">(</span><span class="nx">statusCode</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="kt">byte</span><span class="p">{</span><span class="sc">&#39;\r&#39;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39;\n&#39;</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">k</span><span class="p">,</span><span class="w"> </span><span class="nx">vals</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">headers</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">val</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">vals</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">io</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">k</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="kt">byte</span><span class="p">{</span><span class="sc">&#39;:&#39;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39; &#39;</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">io</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">val</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="kt">byte</span><span class="p">{</span><span class="sc">&#39;\r&#39;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39;\n&#39;</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="kt">byte</span><span class="p">{</span><span class="sc">&#39;\r&#39;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39;\n&#39;</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">addr</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;127.0.0.1:9000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">NewServeMux</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// TODO: handlers go here</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">s</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">Server</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Addr</span><span class="p">:</span><span class="w">    </span><span class="nx">addr</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Handler</span><span class="p">:</span><span class="w"> </span><span class="nx">mux</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;Starting web server: http://%s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">addr</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">go</span><span class="w"> </span><span class="nx">run</span><span class="w"> </span><span class="nx">server</span><span class="o">/</span><span class="nx">main</span><span class="p">.</span><span class="k">go</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">mux</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/headers&#34;</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">w</span><span class="p">.</span><span class="nf">Header</span><span class="p">().</span><span class="nf">Add</span><span class="p">(</span><span class="s">&#34;content-type&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;application/json&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">json</span><span class="p">.</span><span class="nf">NewEncoder</span><span class="p">(</span><span class="nx">w</span><span class="p">).</span><span class="nf">Encode</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">Header</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">})</span><span class="w">
</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl -H <span class="s2">&#34;My-Custom-Header: Hello, world!&#34;</span> http://localhost:9000/headers
</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;Accept&#34;</span>:<span class="o">[</span><span class="s2">&#34;*/*&#34;</span><span class="o">]</span>,<span class="s2">&#34;Host&#34;</span>:<span class="o">[</span><span class="s2">&#34;localhost:9000&#34;</span><span class="o">]</span>,<span class="s2">&#34;My-Custom-Header&#34;</span>:<span class="o">[</span><span class="s2">&#34;Hello, world!&#34;</span><span class="o">]</span>,<span class="s2">&#34;User-Agent&#34;</span>:<span class="o">[</span><span class="s2">&#34;curl/8.7.1&#34;</span><span class="o">]}</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">printf</span> <span class="s2">&#34;GET /headers HTTP/1.0\r\nMy-Custom-Header: Hello from nc!\r\n&#34;</span> <span class="p">|</span> nc localhost <span class="m">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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">mux</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/status/{status}&#34;</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">status</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">strconv</span><span class="p">.</span><span class="nf">ParseInt</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nf">PathValue</span><span class="p">(</span><span class="s">&#34;status&#34;</span><span class="p">),</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">64</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusBadRequest</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">io</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;error: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="nx">status</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">})</span><span class="w">
</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl -I http://localhost:9000/status/404
</span></span><span class="line"><span class="cl">HTTP/1.0 <span class="m">404</span> Not Found
</span></span></code></pre></div><h4 id="nothing">/nothing</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">mux</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/nothing&#34;</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{})</span><span class="w">
</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl http://localhost:9000/nothing
</span></span></code></pre></div><h4 id="echo">/echo</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">mux</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/echo&#34;</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">Body</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">io</span><span class="p">.</span><span class="nf">Copy</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">Body</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">})</span><span class="w">
</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl -X POST -d <span class="s2">&#34;This is some data to echo&#34;</span> http://localhost:9000/echo
</span></span><span class="line"><span class="cl">This is some data to <span class="nb">echo</span>
</span></span></code></pre></div><h4 id="serving-up-my-blog">Serving up my blog</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">mux</span><span class="p">.</span><span class="nf">Handle</span><span class="p">(</span><span class="s">&#34;/&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">FileServer</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nf">Dir</span><span class="p">(</span><span class="s">&#34;public&#34;</span><span class="p">)))</span><span class="w">
</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"
             alt="" 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"
             alt="" 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"
             alt="" 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><div class="chart-wrapper">
<div class="chart">
<canvas id="23e917feb4763f17" data-sort="true"></canvas>
</div>
</div>
<script>
(function() {
window.chartJsPromise = window.chartJsPromise || new Promise((resolve, reject) => {
if (window.Chart) {
resolve(window.Chart);
return;
}
const script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/chart.js";
script.async = true;
script.onload = () => resolve(window.Chart);
script.onerror = () => reject(new Error("Failed to load Chart.js"));
document.head.appendChild(script);
});
window.activeCharts = window.activeCharts || [];
function getThemeColors() {
return {
text: '#ffffff',
grid: 'rgba(255, 255, 255, 0.1)'
};
}
function applyThemeToOptions(options, colors) {
if (!options) return;
if (!options.plugins) options.plugins = {};
if (options.plugins.title) {
options.plugins.title.color = colors.text;
}
if (!options.plugins.legend) options.plugins.legend = {};
if (!options.plugins.legend.labels) options.plugins.legend.labels = {};
options.plugins.legend.labels.color = colors.text;
if (options.scales) {
for (const scaleId in options.scales) {
const scale = options.scales[scaleId];
if (scale && typeof scale === 'object') {
if (!scale.ticks) scale.ticks = {};
scale.ticks.color = colors.text;
if (!scale.grid) scale.grid = {};
scale.grid.color = colors.grid;
}
}
}
}
if (!window.chartThemeObserver) {
window.chartThemeObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'data-theme') {
const colors = getThemeColors();
window.activeCharts.forEach((chart) => {
applyThemeToOptions(chart.options, colors);
chart.update();
});
}
});
});
window.chartThemeObserver.observe(document.documentElement, { attributes: true });
}
window.chartJsPromise.then((Chart) => {
const ctx = document.getElementById('23e917feb4763f17');
if (!ctx) return;
const chartConfig = 
{
  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
    }]
  }
};
const isMobileWidth = window.innerWidth >= 600 ? false : true;
if (!chartConfig.options) chartConfig.options = {};
if (chartConfig.options.responsive === undefined) chartConfig.options.responsive = true;
if (chartConfig.options.maintainAspectRatio === undefined) chartConfig.options.maintainAspectRatio = false;
if (isMobileWidth && chartConfig.data && chartConfig.data.labels) {
chartConfig.data.labels = chartConfig.data.labels.map(function(label) {
if (typeof label === 'string') {
return label
.replace('google.protobuf.Value', 'g.p.Value')
.replace('google.protobuf.Any', 'g.p.Any')
.replace('google.protobuf.', 'g.p.');
}
return label;
});
}
if (isMobileWidth) {
if (!chartConfig.options.scales) chartConfig.options.scales = {};
if (!chartConfig.options.scales.x) chartConfig.options.scales.x = {};
if (!chartConfig.options.scales.x.ticks) chartConfig.options.scales.x.ticks = {};
if (!chartConfig.options.scales.x.ticks.font) chartConfig.options.scales.x.ticks.font = {};
chartConfig.options.scales.x.ticks.font.size = 10;
if (!chartConfig.options.scales.y) chartConfig.options.scales.y = {};
if (!chartConfig.options.scales.y.ticks) chartConfig.options.scales.y.ticks = {};
if (!chartConfig.options.scales.y.ticks.font) chartConfig.options.scales.y.ticks.font = {};
chartConfig.options.scales.y.ticks.font.size = 10;
}
function adjustHeight() {
const isMobile = window.innerWidth >= 600 ? false : true;
const barCount = (chartConfig.data && chartConfig.data.labels) ? chartConfig.data.labels.length : 0;
let chartHeight = 350;
if (chartConfig.type === 'bar' && chartConfig.options.indexAxis === 'y') {
const heightPerBar = isMobile ? 35 : 30;
const extraPadding = isMobile ? 130 : 100;
chartHeight = Math.max(300, (barCount * heightPerBar) + extraPadding);
} else {
chartHeight = isMobile ? 320 : 380;
}
ctx.parentElement.style.height = chartHeight + 'px';
}
adjustHeight();
window.addEventListener('resize', adjustHeight);
if (chartConfig.options && chartConfig.options.plugins && chartConfig.options.plugins.legend && chartConfig.options.plugins.legend.customLegend) { const customItems = chartConfig.options.plugins.legend.customLegend; chartConfig.options.plugins.legend.labels = chartConfig.options.plugins.legend.labels || {}; chartConfig.options.plugins.legend.labels.generateLabels = function(chart) { return customItems.map(function(item) { return { text: item.text, fillStyle: item.color, strokeStyle: item.color, lineWidth: 1, hidden: false, fontColor: '#ffffff', color: '#ffffff' }; }); }; chartConfig.options.plugins.legend.onClick = function() {}; }
const colors = getThemeColors();
applyThemeToOptions(chartConfig.options, colors);
const customLabelPlugin = {
id: 'customLabelPlugin',
afterDatasetsDraw: function(chart) {
if (chart.config.type !== 'bar') return;
const ctx = chart.ctx;
const canvasEl = chart.canvas;
const unit = canvasEl.getAttribute('data-unit');
chart.data.datasets.forEach(function(dataset, datasetIndex) {
const meta = chart.getDatasetMeta(datasetIndex);
meta.data.forEach(function(bar, index) {
const value = dataset.data[index];
if (value === undefined || value === null) return;
if (bar && typeof bar.x === 'number' && chart.isDatasetVisible(datasetIndex)) {
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
const isMobile = window.innerWidth >= 600 ? false : true;
ctx.font = isMobile ? 'bold 10px sans-serif' : 'bold 11px sans-serif';
let text = value.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 2 });
if (unit) {
text += ' ' + unit;
} else {
const label = dataset.label || '';
if (label.toLowerCase().indexOf('bytes') !== -1) {
text += ' B';
} else if (label.toLowerCase().indexOf('ns') !== -1) {
text += ' ns';
}
}
ctx.fillText(text, bar.x + 6, bar.y);
}
});
});
}
};
chartConfig.plugins = chartConfig.plugins || [];
chartConfig.plugins.push(customLabelPlugin);
if (!chartConfig.options.scales) chartConfig.options.scales = {};
if (!chartConfig.options.scales.x) chartConfig.options.scales.x = {};
chartConfig.options.scales.x.grace = isMobileWidth ? '25%' : '15%';
const canvasEl = ctx;
const sortAttr = canvasEl.getAttribute('data-sort');
if (sortAttr !== 'false' && chartConfig.type === 'bar' && chartConfig.data && chartConfig.data.datasets && chartConfig.data.datasets[0]) {
const dataset = chartConfig.data.datasets[0];
const label = (dataset.label || '').toLowerCase();
let sortOrder = 'asc';
if (label.indexOf('rps') !== -1 || label.indexOf('throughput') !== -1 || sortAttr === 'desc') {
sortOrder = 'desc';
}
if (chartConfig.data.labels && dataset.data && chartConfig.data.labels.length === dataset.data.length) {
let items = chartConfig.data.labels.map(function(label, index) {
return {
label: label,
value: dataset.data[index],
backgroundColor: Array.isArray(dataset.backgroundColor) ? dataset.backgroundColor[index] : dataset.backgroundColor,
borderColor: Array.isArray(dataset.borderColor) ? dataset.borderColor[index] : dataset.borderColor
};
});
items.sort(function(a, b) {
if (sortOrder === 'desc') {
return b.value - a.value;
}
return a.value - b.value;
});
chartConfig.data.labels = items.map(function(item) { return item.label; });
dataset.data = items.map(function(item) { return item.value; });
if (Array.isArray(dataset.backgroundColor)) {
dataset.backgroundColor = items.map(function(item) { return item.backgroundColor; });
}
if (Array.isArray(dataset.borderColor)) {
dataset.borderColor = items.map(function(item) { return item.borderColor; });
}
}
}
const chart = new Chart(ctx, chartConfig);
window.activeCharts.push(chart);
if (window.onChartInit) {
window.onChartInit(chart);
}
}, (err) => {
console.error("Chart.js loading error: ", err);
});
})();
</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"
             alt="" 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"
             alt="" 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"
             alt="" 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"
             alt="" 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" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">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" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">&gt; GET /index.html
</span></span></span><span class="line"><span class="cl"><span class="err">&lt; &lt;html&gt;
</span></span></span><span class="line"><span class="cl"><span class="err">&lt; Hello World!
</span></span></span><span class="line"><span class="cl"><span class="err">&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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Server</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Addr</span><span class="w">    </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Handler</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">Handler</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="o">*</span><span class="nx">Server</span><span class="p">)</span><span class="w"> </span><span class="nf">ListenAndServe</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nx">Handler</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nb">panic</span><span class="p">(</span><span class="s">&#34;http server started without a handler&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">l</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nf">Listen</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nx">Addr</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">l</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">l</span><span class="p">.</span><span class="nf">Accept</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">go</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nf">handleConnection</span><span class="p">(</span><span class="nx">conn</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="o">*</span><span class="nx">Server</span><span class="p">)</span><span class="w"> </span><span class="nf">handleConnection</span><span class="p">(</span><span class="nx">conn</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nx">Conn</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">reader</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">bufio</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">(</span><span class="nx">conn</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">line</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reader</span><span class="p">.</span><span class="nf">ReadLine</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fields</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nf">Fields</span><span class="p">(</span><span class="nb">string</span><span class="p">(</span><span class="nx">line</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">fields</span><span class="p">)</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">r</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Method</span><span class="p">:</span><span class="w">     </span><span class="nx">fields</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">URL</span><span class="p">:</span><span class="w">        </span><span class="o">&amp;</span><span class="nx">url</span><span class="p">.</span><span class="nx">URL</span><span class="p">{</span><span class="nx">Scheme</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;http&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">Path</span><span class="p">:</span><span class="w"> </span><span class="nx">fields</span><span class="p">[</span><span class="mi">1</span><span class="p">]},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Proto</span><span class="p">:</span><span class="w">      </span><span class="s">&#34;HTTP/0.9&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">ProtoMajor</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">ProtoMinor</span><span class="p">:</span><span class="w"> </span><span class="mi">9</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">RemoteAddr</span><span class="p">:</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">RemoteAddr</span><span class="p">().</span><span class="nf">String</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">s</span><span class="p">.</span><span class="nx">Handler</span><span class="p">.</span><span class="nf">ServeHTTP</span><span class="p">(</span><span class="nf">newWriter</span><span class="p">(</span><span class="nx">conn</span><span class="p">),</span><span class="w"> </span><span class="nx">r</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">ResponseWriter</span><span class="w"> </span><span class="kd">interface</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nf">Header</span><span class="p">()</span><span class="w"> </span><span class="nx">Header</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nf">Write</span><span class="p">([]</span><span class="kt">byte</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">statusCode</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Here&rsquo;s what our HTTP/0.9 looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">responseBodyWriter</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">conn</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nx">Conn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">Header</span><span class="p">()</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">Header</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// unsupported with HTTP/0.9</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">Write</span><span class="p">(</span><span class="nx">b</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">responseBodyWriter</span><span class="p">)</span><span class="w"> </span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">statusCode</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// unsupported with HTTP/0.9</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">newWriter</span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nx">Conn</span><span class="p">)</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">responseBodyWriter</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">conn</span><span class="p">:</span><span class="w"> </span><span class="nx">c</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">addr</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;127.0.0.1:9000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">s</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">Server</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Addr</span><span class="p">:</span><span class="w"> </span><span class="nx">addr</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Handler</span><span class="p">:</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">HandlerFunc</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">w</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="s">&#34;Hello World!&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">log</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;Listening on %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">addr</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ go run server/main.go
</span></span><span class="line"><span class="cl">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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl --http0.9 http://127.0.0.1:9000/this/is/a/test
</span></span><span class="line"><span class="cl">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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">--http0.9
</span></span><span class="line"><span class="cl">       <span class="o">(</span>HTTP<span class="o">)</span> Tells curl to be fine with HTTP version 0.9 response.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       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 class="k">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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl --http1.0 --http0.9 http://127.0.0.1:9000/this/is/a/test
</span></span><span class="line"><span class="cl">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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl http://127.0.0.1:9000/this/is/a/test
</span></span><span class="line"><span class="cl">curl: <span class="o">(</span>1<span class="o">)</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">echo</span> GET this/is/a/test <span class="p">|</span> ncat 127.0.0.1 <span class="m">9000</span>
</span></span><span class="line"><span class="cl">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"
             alt="" 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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nf">Dial</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;127.0.0.1:9000&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;err: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="s">&#34;GET /this/is/a/test\r\n&#34;</span><span class="p">));</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;err: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">body</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">ReadAll</span><span class="p">(</span><span class="nx">conn</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;err: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nb">string</span><span class="p">(</span><span class="nx">body</span><span class="p">))</span><span class="w">
</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="err">$</span><span class="w"> </span><span class="k">go</span><span class="w"> </span><span class="nx">run</span><span class="w"> </span><span class="nx">client</span><span class="o">/</span><span class="nx">main</span><span class="p">.</span><span class="k">go</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">Hello</span><span class="w"> </span><span class="nx">World</span><span class="p">!</span><span class="w">
</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>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"
             alt="" 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"
             alt="" 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"
             alt="" 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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">NewServeMux</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Implementation is only in the full source</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="p">.</span><span class="nf">Handle</span><span class="p">(</span><span class="nx">elizav1connect</span><span class="p">.</span><span class="nf">NewElizaServiceHandler</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">server</span><span class="p">{}))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">addr</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;127.0.0.1:6660&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;Starting connectrpc on %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">addr</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">h3srv</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http3</span><span class="p">.</span><span class="nx">Server</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Addr</span><span class="p">:</span><span class="w">    </span><span class="nx">addr</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Handler</span><span class="p">:</span><span class="w"> </span><span class="nx">mux</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">h3srv</span><span class="p">.</span><span class="nf">ListenAndServeTLS</span><span class="p">(</span><span class="s">&#34;cert.crt&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;cert.key&#34;</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;error: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">NewServeMux</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="p">.</span><span class="nf">Handle</span><span class="p">(</span><span class="nx">elizav1connect</span><span class="p">.</span><span class="nf">NewElizaServiceHandler</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">server</span><span class="p">{}))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">addr</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;127.0.0.1:6660&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;Starting connectrpc on %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">addr</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">h3srv</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http3</span><span class="p">.</span><span class="nx">Server</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Addr</span><span class="p">:</span><span class="w">    </span><span class="nx">addr</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Handler</span><span class="p">:</span><span class="w"> </span><span class="nx">mux</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">srv</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">Server</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Addr</span><span class="p">:</span><span class="w">    </span><span class="nx">addr</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Handler</span><span class="p">:</span><span class="w"> </span><span class="nx">h2c</span><span class="p">.</span><span class="nf">NewHandler</span><span class="p">(</span><span class="nx">mux</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">http2</span><span class="p">.</span><span class="nx">Server</span><span class="p">{}),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">eg</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">errgroup</span><span class="p">.</span><span class="nf">WithContext</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">eg</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">h3srv</span><span class="p">.</span><span class="nf">ListenAndServeTLS</span><span class="p">(</span><span class="s">&#34;cert.crt&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;cert.key&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">eg</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">srv</span><span class="p">.</span><span class="nf">ListenAndServeTLS</span><span class="p">(</span><span class="s">&#34;cert.crt&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;cert.key&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">eg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;error: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">openssl req -new -newkey rsa:4096 -days <span class="m">365</span> -nodes -x509 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -subj <span class="s2">&#34;/C=DK/L=Copenhagen/O=kmcd/CN=local.kmcd.dev&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">url</span><span class="w">        </span><span class="p">=</span><span class="w"> </span><span class="s">&#34;https://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">reqBody</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`{&#34;sentence&#34;: &#34;Hello World!&#34;}`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">roundTripper</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">http3</span><span class="p">.</span><span class="nx">RoundTripper</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">TLSClientConfig</span><span class="p">:</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">tls</span><span class="p">.</span><span class="nx">Config</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="c1">// we need this because our certificate is self signed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">InsecureSkipVerify</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">roundTripper</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">client</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">http</span><span class="p">.</span><span class="nx">Client</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Transport</span><span class="p">:</span><span class="w"> </span><span class="nx">roundTripper</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;connect: &#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">url</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;send: &#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">reqBody</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">NewRequest</span><span class="p">(</span><span class="s">&#34;POST&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">url</span><span class="p">,</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">(</span><span class="nx">reqBody</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;error: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="s">&#34;Content-Type&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;application/json&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">resp</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">client</span><span class="p">.</span><span class="nf">Do</span><span class="p">(</span><span class="nx">req</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;error: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">body</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">ReadAll</span><span class="p">(</span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;error: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;recv: &#34;</span><span class="p">,</span><span class="w"> </span><span class="nb">string</span><span class="p">(</span><span class="nx">body</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">2024/07/06 12:57:24 connect:  https://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say
</span></span><span class="line"><span class="cl">2024/07/06 12:57:24 send:  <span class="o">{</span><span class="s2">&#34;sentence&#34;</span>: <span class="s2">&#34;Hello World!&#34;</span><span class="o">}</span>
</span></span><span class="line"><span class="cl">2024/07/06 12:57:24 recv:  <span class="o">{</span><span class="s2">&#34;sentence&#34;</span>:<span class="s2">&#34;Hello World!&#34;</span><span class="o">}</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" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">url</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&#34;https://127.0.0.1:6660&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">var</span><span class="w"> </span><span class="nx">reqBody</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">elizav1</span><span class="p">.</span><span class="nx">SayRequest</span><span class="p">{</span><span class="nx">Sentence</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;Hello World!&#34;</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">roundTripper</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">http3</span><span class="p">.</span><span class="nx">RoundTripper</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">TLSClientConfig</span><span class="p">:</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">tls</span><span class="p">.</span><span class="nx">Config</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">InsecureSkipVerify</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">roundTripper</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">client</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">http</span><span class="p">.</span><span class="nx">Client</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Transport</span><span class="p">:</span><span class="w"> </span><span class="nx">roundTripper</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">svcClient</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">elizav1connect</span><span class="p">.</span><span class="nf">NewElizaServiceClient</span><span class="p">(</span><span class="nx">client</span><span class="p">,</span><span class="w"> </span><span class="nx">url</span><span class="p">,</span><span class="w"> </span><span class="nx">connect</span><span class="p">.</span><span class="nf">WithGRPC</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;connect: &#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">url</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;send: &#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">reqBody</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">resp</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">svcClient</span><span class="p">.</span><span class="nf">Say</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span><span class="w"> </span><span class="nx">connect</span><span class="p">.</span><span class="nf">NewRequest</span><span class="p">(</span><span class="nx">reqBody</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;error: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;recv: &#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">Msg</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">2024/07/06 12:57:12 connect:  https://127.0.0.1:6660
</span></span><span class="line"><span class="cl">2024/07/06 12:57:12 send:  sentence:<span class="s2">&#34;Hello World!&#34;</span>
</span></span><span class="line"><span class="cl">2024/07/06 12:57:14 recv:  sentence:<span class="s2">&#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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --json <span class="s1">&#39;{&#34;sentence&#34;: &#34;Hello World!&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --http3 -k -v <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  https://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say
</span></span><span class="line"><span class="cl">curl: option --http3: the installed libcurl version doesn<span class="s1">&#39;t support this
</span></span></span><span class="line"><span class="cl"><span class="s1">curl: try &#39;</span>curl --help<span class="s1">&#39; or &#39;</span>curl --manual<span class="err">&#39;</span> <span class="k">for</span> more information
</span></span></code></pre></div>
    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/grpc-over-http3/suprised_hu_a42315c900e59c3f.webp"
             alt="" 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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --json <span class="s1">&#39;{&#34;sentence&#34;: &#34;Hello World!&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --http3 -k -v <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  https://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say
</span></span><span class="line"><span class="cl">*   Trying 127.0.0.1:6660...
</span></span><span class="line"><span class="cl">* Server certificate:
</span></span><span class="line"><span class="cl">*  subject: <span class="nv">C</span><span class="o">=</span>DK<span class="p">;</span> <span class="nv">L</span><span class="o">=</span>Copenhagen<span class="p">;</span> <span class="nv">O</span><span class="o">=</span>kmcd<span class="p">;</span> <span class="nv">CN</span><span class="o">=</span>local.kmcd.dev
</span></span><span class="line"><span class="cl">*  start date: Jul  <span class="m">6</span> 06:14:48 <span class="m">2024</span> GMT
</span></span><span class="line"><span class="cl">*  expire date: Jul  <span class="m">6</span> 06:14:48 <span class="m">2025</span> GMT
</span></span><span class="line"><span class="cl">*  issuer: <span class="nv">C</span><span class="o">=</span>DK<span class="p">;</span> <span class="nv">L</span><span class="o">=</span>Copenhagen<span class="p">;</span> <span class="nv">O</span><span class="o">=</span>kmcd<span class="p">;</span> <span class="nv">CN</span><span class="o">=</span>local.kmcd.dev
</span></span><span class="line"><span class="cl">*  SSL certificate verify result: self signed certificate <span class="o">(</span>18<span class="o">)</span>, continuing anyway.
</span></span><span class="line"><span class="cl">* Connected to 127.0.0.1 <span class="o">(</span>127.0.0.1<span class="o">)</span> port <span class="m">6660</span>
</span></span><span class="line"><span class="cl">* using HTTP/3
</span></span><span class="line"><span class="cl">* <span class="o">[</span>HTTP/3<span class="o">]</span> <span class="o">[</span>0<span class="o">]</span> OPENED stream <span class="k">for</span> https://127.0.0.1:6660/connectrpc.eliza.v1.ElizaService/Say
</span></span><span class="line"><span class="cl">* <span class="o">[</span>HTTP/3<span class="o">]</span> <span class="o">[</span>0<span class="o">]</span> <span class="o">[</span>:method: POST<span class="o">]</span>
</span></span><span class="line"><span class="cl">* <span class="o">[</span>HTTP/3<span class="o">]</span> <span class="o">[</span>0<span class="o">]</span> <span class="o">[</span>:scheme: https<span class="o">]</span>
</span></span><span class="line"><span class="cl">* <span class="o">[</span>HTTP/3<span class="o">]</span> <span class="o">[</span>0<span class="o">]</span> <span class="o">[</span>:authority: 127.0.0.1:6660<span class="o">]</span>
</span></span><span class="line"><span class="cl">* <span class="o">[</span>HTTP/3<span class="o">]</span> <span class="o">[</span>0<span class="o">]</span> <span class="o">[</span>:path: /connectrpc.eliza.v1.ElizaService/Say<span class="o">]</span>
</span></span><span class="line"><span class="cl">* <span class="o">[</span>HTTP/3<span class="o">]</span> <span class="o">[</span>0<span class="o">]</span> <span class="o">[</span>user-agent: curl/8.9.0-DEV<span class="o">]</span>
</span></span><span class="line"><span class="cl">* <span class="o">[</span>HTTP/3<span class="o">]</span> <span class="o">[</span>0<span class="o">]</span> <span class="o">[</span>content-type: application/json<span class="o">]</span>
</span></span><span class="line"><span class="cl">* <span class="o">[</span>HTTP/3<span class="o">]</span> <span class="o">[</span>0<span class="o">]</span> <span class="o">[</span>accept: application/json<span class="o">]</span>
</span></span><span class="line"><span class="cl">* <span class="o">[</span>HTTP/3<span class="o">]</span> <span class="o">[</span>0<span class="o">]</span> <span class="o">[</span>content-length: 28<span class="o">]</span>
</span></span><span class="line"><span class="cl">&gt; POST /connectrpc.eliza.v1.ElizaService/Say HTTP/3
</span></span><span class="line"><span class="cl">&gt; Host: 127.0.0.1:6660
</span></span><span class="line"><span class="cl">&gt; User-Agent: curl/8.9.0-DEV
</span></span><span class="line"><span class="cl">&gt; Content-Type: application/json
</span></span><span class="line"><span class="cl">&gt; Accept: application/json
</span></span><span class="line"><span class="cl">&gt; Content-Length: <span class="m">28</span>
</span></span><span class="line"><span class="cl">&gt;
</span></span><span class="line"><span class="cl">* upload completely sent off: <span class="m">28</span> bytes
</span></span><span class="line"><span class="cl">&lt; HTTP/3 <span class="m">200</span>
</span></span><span class="line"><span class="cl">&lt; content-type: application/json
</span></span><span class="line"><span class="cl">&lt; accept-encoding: gzip
</span></span><span class="line"><span class="cl">&lt; date: Sat, <span class="m">06</span> Jul <span class="m">2024</span> 13:17:12 GMT
</span></span><span class="line"><span class="cl">&lt; content-length: <span class="m">27</span>
</span></span><span class="line"><span class="cl">&lt;
</span></span><span class="line"><span class="cl">* Connection <span class="c1">#0 to host 127.0.0.1 left intact</span>
</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;sentence&#34;</span>:<span class="s2">&#34;Hello World!&#34;</span><span class="o">}</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ buf curl <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --http3 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --schema<span class="o">=</span>buf.build/connectrpc/eliza <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -d <span class="s1">&#39;{&#34;sentence&#34;: &#34;Hello world!&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say
</span></span><span class="line"><span class="cl"><span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;sentence&#34;</span>: <span class="s2">&#34;Hello...I&#39;m glad you could drop by today.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">$buf</span> curl <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --http3 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --protocol<span class="o">=</span>grpcweb <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --schema<span class="o">=</span>buf.build/connectrpc/eliza <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -d <span class="s1">&#39;{&#34;sentence&#34;: &#34;Hello world!&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say
</span></span><span class="line"><span class="cl"><span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;sentence&#34;</span>: <span class="s2">&#34;Hello, how are you feeling today?&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>And now the classic gRPC:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ buf curl <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --http3 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --protocol<span class="o">=</span>grpc <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --schema<span class="o">=</span>buf.build/connectrpc/eliza <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -d <span class="s1">&#39;{&#34;sentence&#34;: &#34;Hello world!&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say
</span></span><span class="line"><span class="cl"><span class="o">{</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;code&#34;</span>: <span class="s2">&#34;internal&#34;</span>,
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;message&#34;</span>: <span class="s2">&#34;protocol error: no Grpc-Status trailer: unexpected EOF&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</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"
             alt="" 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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ buf curl <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --http3 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --protocol<span class="o">=</span>grpc <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --schema<span class="o">=</span>buf.build/connectrpc/eliza <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -d <span class="s1">&#39;{&#34;sentence&#34;: &#34;Hello world!&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say
</span></span><span class="line"><span class="cl"><span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;sentence&#34;</span>: <span class="s2">&#34;Hello there...how are you today?&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</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"
             alt="" 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"
             alt="" 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" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="n">map</span><span class="p">&lt;</span><span class="n">key_type</span><span class="p">,</span> <span class="n">value_type</span><span class="p">&gt;</span> <span class="n">map_field</span> <span class="o">=</span> <span class="n">N</span><span class="p">;</span><span class="err">
</span></span></span></code></pre></div><p>transforms into:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="kd">message</span> <span class="nc">MapFieldEntry</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="n">key_type</span> <span class="n">key</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="n">value_type</span> <span class="n">value</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">repeated</span> <span class="n">MapFieldEntry</span> <span class="n">map_field</span> <span class="o">=</span> <span class="n">N</span><span class="p">;</span><span class="err">
</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" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="n">syntax</span> <span class="o">=</span> <span class="s">&#34;proto3&#34;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kn">package</span> <span class="nn">your</span><span class="o">.</span><span class="n">service.v1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">option</span> <span class="n">go_package</span> <span class="o">=</span> <span class="s">&#34;github.com/yourorg/yourprotos/gen/go/your/service/v1&#34;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">import</span> <span class="s">&#34;google/api/annotations.proto&#34;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">StringMessage</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">value</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">service</span> <span class="n">YourService</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">rpc</span> <span class="n">Echo</span><span class="p">(</span><span class="n">StringMessage</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">StringMessage</span><span class="p">)</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>    <span class="k">option</span> <span class="p">(</span><span class="n">google.api.http</span><span class="p">)</span> <span class="o">=</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>      <span class="n">post</span><span class="o">:</span> <span class="s">&#34;/v1/example/echo&#34;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>      <span class="n">body</span><span class="o">:</span> <span class="s">&#34;*&#34;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>    <span class="p">};</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl -XPOST <span class="s1">&#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" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl --header <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --data <span class="s1">&#39;{&#34;sentence&#34;: &#34;I feel happy.&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></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>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"
             alt="" 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" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="n">syntax</span> <span class="o">=</span> <span class="s">&#34;proto3&#34;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kn">package</span> <span class="nn">file_service</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">service</span> <span class="n">FileService</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>   <span class="k">rpc</span> <span class="n">Upload</span><span class="p">(</span><span class="n">stream</span> <span class="n">UploadRequest</span><span class="p">)</span> <span class="k">returns</span><span class="p">(</span><span class="n">UploadResponse</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">UploadRequest</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>    <span class="kt">string</span> <span class="n">file_name</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>    <span class="kt">bytes</span> <span class="n">chunk</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">UploadResponse</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">etag</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</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></channel></rss>